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 标准库的深水区。下一篇,我们将学习如何操作文件系统,读写文件。准备好和磁盘打交道了吗?