Ziglings 笔记 71: 编译期循环 (Inline For)
拒绝重复劳动
在第 65 个练习中,为了打印 Narcissus 结构体的非 void 字段,我们要么手动写三遍 if,要么在心中默默流泪。那种写法不仅丑陋,而且如果我们给结构体加了一个新字段,还得记得去更新打印代码。
Ziglings 的第 71 个练习带来了救星:inline for。
挑战:自动化反射
我们需要遍历 Narcissus 的所有字段。
- 这是一个只在编译期已知的信息列表。
- 我们需要根据字段类型(是否为 void)动态生成打印语句。
- 普通的
for循环做不到这一点,因为它是在程序运行起来后才工作的。
解决方案
使用 inline for 让编译器帮我们“写代码”:
const print = @import("std").debug.print;
const Narcissus = struct {
me: *Narcissus = undefined,
myself: *Narcissus = undefined,
echo: void = undefined,
};
pub fn main() void {
print("Narcissus has room in his heart for:", .{});
// 获取编译期的字段信息列表
const fields = @typeInfo(Narcissus).@"struct".fields;
// 核心语法:inline for
// 编译器会把这个循环展开。
// 如果 fields 有 3 个元素,编译器就会把下面的代码块复制 3 次。
inline for (fields) |field| {
// 这个 if 是在编译期判断的!
// 如果类型是 void,这段打印代码直接被编译器删除,不会进入最终的可执行文件。
if (field.type != void) {
print(" {s}", .{field.name});
}
}
print(".\n", .{});
}
核心知识点总结
1. 循环展开 (Loop Unrolling)
当你写下 inline for 时,你实际上是在对编译器说:“别生成循环指令,请把这段代码给我重复 N 遍,每一遍填入不同的参数。”
这在处理类型列表、异构元组 (Tuples) 或 编译期配置 时非常有用。
2. 这里的代码发生了什么?
编译器看到 inline for 后,实际生成的逻辑等价于:
// 迭代 1: field = me
print(" {s}", .{"me"});
// 迭代 2: field = myself
print(" {s}", .{"myself"});
// 迭代 3: field = echo
// (if field.type != void 为假,代码被删除)
最终程序运行非常快,因为它只执行了两条打印语句,没有循环开销。
3. inline 的能力边界
- 只能用于能在编译期确定长度的序列(如数组、元组、Comptime Slice)。
- 如果序列太长,
inline for会导致生成的二进制文件体积膨胀(Code Bloat),所以要适度使用。
后续预告:我们已经彻底征服了 Comptime。现在,我们将进入 Zig 标准库的深水区。下一篇,我们将学习如何操作文件系统,读写文件。准备好和磁盘打交道了吗?