comptime 不是注解糖。它是在说:这段代码在编译器里跑,结果变成常量、类型或内联后的机器码。
最直观的例子是查找表。与其启动后再算 256 个入口,不如让编译器算完。
1fn nibbleCount(byte: u8) u8 {2 var n: u8 = 0;3 var value = byte;4 inline while (value != 0) : (value >>= 1) {5 n += @as(u8, @intCast(value & 1));6 }7 return n;8}9 10const popcount_table: [256]u8 = blk: {11 var table: [256]u8 = undefined;12 for (&table, 0..) |*slot, i| {13 slot.* = nibbleCount(@intCast(i));14 }15 break :blk table;16};17 18pub fn popcount(byte: u8) u8 {19 return popcount_table[byte];20}inline while 把循环在编译期展开。blk: 是一个立即执行的常量块。运行时的 popcount 只剩一次数组取值。
类型也是值
Zig 的类型可以当作 comptime 参数传来传去。这比 C++ 模板读起来更像普通函数。
1pub fn max(comptime T: type, a: T, b: T) T {2 return if (a > b) a else b;3}4 5test "max ints" {6 try std.testing.expectEqual(@as(u32, 9), max(u32, 3, 9));7}没有泛型关键字。T 就是一个编译期的类型值。错误会指向你写的那一行,而不是展开后三百行的模板地狱。
把能确定的事情提前到编译期,运行时才能只做还不确定的事情。
编译期把类型当值传来传去,和 判别联合 一样,都是把决策提前。内存谁说了算,则要看 rust-ownership。
Zig 对「没有隐藏控制流」偏执得很有用。comptime 是少数被允许的魔法,而且它把魔法写在明面上。
