Ziglings 笔记 92: 编译期多态 (Inline Else)

谁说 Zig 没有接口?

在 Ziglings 的第 92 个练习中,我们解决了一个经典的架构问题:如何统一处理不同的数据类型?

Doctor Zoraptera 厌倦了每次添加新昆虫都要去修改巨大的 switch 语句。她希望代码能像面向对象语言那样,让每个对象自己负责打印自己,而外层容器只需要统一调用 print()

Zig 给出的答案是:union + inline else

挑战:统一的昆虫报告

我们有三种昆虫:Ant, Bee, Grasshopper。它们都各自实现了 print() 方法。 我们将它们包装在一个名为 Insect 的联合体中。 我们需要在 main 函数中遍历昆虫数组,并调用它们各自的打印方法。

解决方案

首先,让我们看看 Insect 联合体是如何利用 inline else 实现“接口”功能的:

const Insect = union(enum) {
    ant: Ant,
    bee: Bee,
    grasshopper: Grasshopper,

    pub fn print(self: Insect) void {
        // 魔法所在:inline else
        // 编译器会自动为联合体中的每一个成员生成代码分支。
        // |case| 捕获的是具体的昆虫实例(例如 Ant 实例)。
        // 只要该实例有 print() 方法,这里就能编译通过。
        switch (self) {
            inline else => |case| case.print(),
        }
    }
};

然后在 main 函数中,调用变得异常简单:

pub fn main() !void {
    const my_insects = [_]Insect{
        Insect{ .ant = Ant{ .still_alive = true } },
        Insect{ .bee = Bee{ .flowers_visited = 17 } },
        Insect{ .grasshopper = Grasshopper{ .distance_hopped = 32 } },
    };

    std.debug.print("Daily Insect Report:\n", .{});
    
    for (my_insects) |insect| {
        // 核心修复:直接调用包装后的 print 方法
        // 无论它是蚂蚁还是蚱蜢,Insect.print 都会分发到正确的逻辑
        insect.print();
    }
}

核心知识点总结

1. 编译期展开

当编译器看到 inline else 时,它实际上是在帮我们写代码。它会遍历 Insect 的所有成员,检查它们是否满足调用要求(即拥有 print 方法),然后生成对应的 switch case。

2. 零成本抽象

这种多态是静态的。

  • 在 Java 中,调用接口通常涉及虚函数表 (vtable) 查找,有运行时开销。
  • 在 Zig 中,由于 Switch 是在编译期展开的,CPU 直接跳转到对应的函数地址。这使得 Zig 的抽象在运行时几乎是零成本的。

3. 开闭原则 (Open/Closed Principle)

这种设计符合软件工程中的开闭原则:对扩展开放,对修改关闭。 如果我们想加一个 Butterfly,只需要定义结构体并把它加入 Insect 联合体。Insect.print 函数本身完全不需要改动,它会自动支持蝴蝶。


后续预告:我们已经彻底掌握了 Zig 的 Comptime 元编程。这是 Zig 最强大的武器。接下来的练习将进入标准库的实战演练,比如如何处理可选的指针和切片?下一篇我们将复习 Sentinels(哨兵)。