Ziglings 笔记 66: 编译期与运行时 (Comptime vs Runtime)

两个世界

在 Ziglings 的第 66 个练习中,我们正式接触了 Zig 的杀手级特性 —— Comptime

在大多数语言中,写代码就是为了在运行时 (Runtime) 执行。但在 Zig 中,我们有一半的工作其实是在编译时 (Compile Time) 完成的。理解这两个“世界”的边界,是掌握 Zig 的关键。

挑战:类型的不确定性

代码中有两个常量和两个变量,被赋予了相同的数值。 常量 const 能够自动推导类型,但变量 var 却报错了。为什么编译器这么双标?

const const_int = 12345;     // ✅ 正常工作
var var_int = 12345;         // ❌ 编译错误!

解决方案

我们需要为 var 变量显式指定运行时类型:

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

pub fn main() void {
    // === 编译期世界 ===
    // 这些值只存在于编译器内部。
    // 类型推导为 comptime_int / comptime_float (任意精度)。
    const const_int = 12345;
    const const_float = 987.654;

    print("Immutable: {}, {d:.3}; ", .{ const_int, const_float });

    // === 运行时世界 ===
    // 这里的变量需要在程序运行时的内存中占位。
    // 内存必须有固定大小,所以必须显式标注类型 (u32, f32)。
    
    // 修复 1: 指定整数类型 u32
    var var_int: u32 = 12345;
    
    // 修复 2: 指定浮点类型 f32
    var var_float: f32 = 987.654;

    // 可以在运行时修改它们
    var_int = 54321;
    var_float = 456.789;

    print("Mutable: {}, {d:.3}; ", .{ var_int, var_float });

    // 见证真相的时刻:查看类型
    // 输出: comptime_int, comptime_float, u32, f32
    print("Types: {}, {}, {}, {}\n", .{
        @TypeOf(const_int),
        @TypeOf(const_float),
        @TypeOf(var_int),
        @TypeOf(var_float),
    });
}

核心知识点总结

1. comptime_int 是什么?

它不是 int,也不是 long。它是一个数学意义上的纯数字。 在编译阶段,12345 只是一个抽象的概念。直到你把它赋值给 u32,它才变成了占用 4 个字节的二进制数据。

2. 为什么 var 需要显式类型?

var 意味着“请在栈上给我留块地”。 如果编译器只知道初始值是 comptime_int(可以是无限大),它就不知道该留多大的地(1字节?8字节?)。所以,程序员必须明确告知:“我要 32 位 (u32) 的空间”。

3. const 的特权

对于 const,编译器通常不需要为它分配内存地址。它经常被编译器直接内联 (Inline) 到指令中,或者在常量折叠优化中消失。因此,它不需要固定的大小限制。


后续预告:既然我们知道了 comptime 的基本概念,那么我们能不能写一段代码,让它只在编译的时候运行,而在程序启动前就已经算好结果了呢?下一篇我们将学习 comptime 关键字。