Ziglings 笔记 35: 告别魔法数字 (Enums)
给数字起个名字
在上一篇关于 unreachable 的笔记中,我们写了一个微型虚拟机。虽然它能工作,但代码里充斥着 1, 2, 3 这样的“魔法数字”,而且被迫写了一个 else => unreachable 来安抚编译器。
Ziglings 的第 35 个练习向我们展示了更好的解决方案:枚举 (Enums)。
挑战:让代码更安全
我们需要改造之前的虚拟机,不再使用 u8 来代表指令,而是使用自定义的枚举类型。
解决方案
这是使用 enum 改造后的代码:
const std = @import("std");
// 1. 定义枚举
// 我们创建了一个名为 Ops 的新类型,它只有三个可能的值。
const Ops = enum { inc, dec, pow };
pub fn main() void {
// 2. 使用枚举
// 数组的类型现在是 [_]Ops,这意味着它只能存放 Ops 的成员。
// 如果你试图在这里写一个 4,编译器会直接报错,而不是等到运行时崩溃。
const operations = [_]Ops{
Ops.inc,
Ops.inc,
Ops.inc,
Ops.pow,
Ops.dec,
Ops.dec,
};
var current_value: u32 = 0;
for (operations) |op| {
switch (op) {
Ops.inc => {
current_value += 1;
},
Ops.dec => {
current_value -= 1;
},
Ops.pow => {
current_value *= current_value;
},
// 3. 不需要 else!
// 因为编译器知道 Ops 只有三个值,而我们已经全部处理了。
// 这种“刚刚好”的感觉体现了类型系统的强大。
}
std.debug.print("{} ", .{current_value});
}
std.debug.print("\n", .{});
}
核心知识点总结
1. 为什么用枚举?
- 可读性:
Ops.inc显然比1更容易理解。 - 安全性:枚举创建了一个封闭的集合。你不可能不小心传入一个无效的指令。
2. 完美的 Switch
这是 Zig 穷尽性检查(Exhaustiveness Check)的高光时刻。
- 对于 u8:编译器认为有 256 种可能,你必须处理所有情况(通常用
else)。 - 对于 Enum:编译器知道确切的成员数量。如果你处理了所有成员,就不需要
else。 - 维护性:如果你将来给
Ops增加了一个新指令div,编译器会立即报错指出switch缺少处理,强迫你更新逻辑。这防止了代码腐烂。
后续预告:枚举只能是一组名字吗?在 Zig 中,枚举其实非常强大,它们甚至可以拥有自己的方法(Methods)。下一篇我们将给枚举加上“超能力”。