Ziglings 笔记 74: 此时无声胜有声 (Implicit Comptime)
没必要重复说
在 Ziglings 的第 74 个练习中,我们学习了一个反直觉的道理:知道何时不写 comptime 和知道何时写它一样重要。
Zig 编译器非常聪明。在很多“显而易见”需要编译期求值的地方(比如全局变量初始化、类型定义),它是自动开启 Comptime 模式的。在这些地方加关键字不仅多余,还会让代码这就“有味道” (Smelly)。
挑战:寻找唯一的 Comptime
我们需要定义一个函数 makeLlamas,它返回一个数组。
这就涉及到了我们在第 70 题学到的知识:如果返回值的类型(数组长度)依赖于参数,那么这个参数必须是编译期已知的。
题目限制我们只能使用一次 comptime 关键字。
解决方案
虽然 llama_count 是全局常量(隐式 comptime),虽然 llamas 的初始化也是在顶层(隐式 comptime),但函数定义的参数默认不是。
const print = @import("std").debug.print;
// 全局作用域:隐式 comptime
const llama_count = 5;
// 全局作用域:隐式 comptime
// 编译器会在这里执行 makeLlamas 函数
const llamas = makeLlamas(llama_count);
// 修复点:函数参数
// 我们需要返回 [count]u8。因为数组长度必须在编译期确定,
// 所以 count 必须标记为 comptime。
fn makeLlamas(comptime count: usize) [count]u8 {
var temp: [count]u8 = undefined;
var i = 0;
// 注意:这里的 while 不需要 inline
// 因为 count 是 comptime 的,编译器有能力优化这个简单的循环
while (i < count) : (i += 1) {
temp[i] = i;
}
return temp;
}
pub fn main() void {
print("My llama value is {}.\n", .{llamas[2]});
}
核心知识点总结
1. 哪里是隐式的?
记住这个列表,并在写代码时以此为准:
- 文件级/结构体级作用域:
const x = ... - 类型声明:
fn foo(a: Type) ReturnType中的类型部分。 - 内联循环:
inline while (cond)中的cond。
2. 为什么参数必须显式?
函数 makeLlamas 可能被用于运行时上下文,也可能被用于编译期上下文。
默认情况下,Zig 假设参数是运行时的(为了灵活性)。
只有当我们强制要求该参数用于定义类型(如 [count]T)或用于 comptime 逻辑时,我们才显式加上 comptime 关键字。
3. 代码的整洁之道
不要在代码里撒胡椒面一样到处撒 comptime。
如果代码能编译通过且运行符合预期,通常说明编译器已经理解了你的意图。过度显式反而会增加阅读负担。
后续预告:我们已经彻底搞懂了 Zig 的语法核心和元编程。接下来的练习将进入一个新的篇章:标准库探秘。我们将从最常用的 Read/Write (IO) 开始。准备好和控制台进行更复杂的交互了吗?