Ziglings 笔记 67: 穿越时空的变量 (Comptime Var)

编译器的草稿纸

在 Ziglings 的第 67 个练习中,我们遇到了 comptime var

通常我们认为,“变量 (Variable)”是程序运行时改变的东西,“常量 (Constant)”是编译时确定的东西。但 Zig 打破了这个界限,它允许我们在编译阶段拥有可变的变量

挑战:动态的静态数组

我们需要声明四个数组 a1, a2, a3, a4。 有趣的是,它们的长度是递增的:1, 2, 3, 4。 为了避免硬编码数字,我们想用一个 count 变量来控制长度。

如果在 C 语言中写 int len = 1; char arr[len];(非 VLA 情况),编译器会报错,因为数组长度必须是常量表达式。

解决方案

在 Zig 中,我们可以告诉编译器:“这个 count 变量只给你用,请你在编译的时候把它算好。”

const print = @import("std").debug.print;

pub fn main() void {
    // 核心:comptime var
    // 这个变量在程序运行前就会被计算和消除
    comptime var count = 0;

    // 编译期执行:count = 1
    count += 1;
    // 编译器看到的是:const a1: [1]u8 = .{'A'} ** 1;
    const a1: [count]u8 = .{'A'} ** count;

    // 编译期执行:count = 2
    count += 1;
    // 编译器看到的是:const a2: [2]u8 = .{'B'} ** 2;
    const a2: [count]u8 = .{'B'} ** count;

    count += 1;
    const a3: [count]u8 = .{'C'} ** count;

    count += 1;
    const a4: [count]u8 = .{'D'} ** count;

    print("{s} {s} {s} {s}\n", .{ a1, a2, a3, a4 });
}

核心知识点总结

1. 编译期执行流

使用 comptime var,我们实际上是在编写一段在编译器内部运行的脚本。 编译器在解析代码时,会像解释器一样执行这些加法操作。等到生成最终的机器码时,count 变量已经完成了它的历史使命,不复存在了。

2. 为什么需要它?

这让 Zig 拥有了极其强大的元编程能力。 想象一下,你可以写一个 for 循环,在编译期计算出正弦表,或者根据配置生成不同长度的结构体字段。这一切都没有运行时开销(Zero Runtime Overhead)。

3. @compileLog

Zig 提供了一个调试工具 @compileLog(args)。如果你想知道在编译的某一步 count 到底变成了多少,可以插入这就话。编译器会打印出值,并以此为由停止编译(视其为一种错误),强迫你查看输出。


后续预告:既然我们可以在编译期修改变量,那能不能在编译期执行 if/else 判断呢?下一篇我们将学习 comptime if,看看如何根据条件有选择地编译代码。