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(哨兵)。