Ziglings 笔记 82: 元组 (Tuples) - 披着马甲的结构体

殊途同归

在 Ziglings 的第 82 个练习中,我们揭开了一个大秘密:Zig 中没有专门的元组类型

我们在 Python 或 Rust 中熟悉的 (1, "a") 元组,在 Zig 中其实就是一个匿名结构体,只不过它的字段名被编译器自动命名为了 "0", "1", "2"… 这种设计不仅简化了语言核心,还意味着我们用来操作结构体的所有工具(反射、内联循环)都能直接用于元组。

挑战:通用的元组打印机

我们需要编写一个 printTuple 函数。

  • 它能接收任何元组(使用 anytype)。
  • 它能遍历元组的所有字段。
  • 它能打印出每个字段的名称(如 “0”)、类型(如 bool)和值。

解决方案

我们需要结合使用 comptime 反射和 inline for 循环:

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

pub fn main() void {
    // 定义一个元组
    // 本质上是 struct { @"0": bool, @"1": bool, ... }
    const foo = .{
        true,
        false,
        @as(i32, 42),
        @as(f32, 3.141592),
    };

    printTuple(foo);
}

fn printTuple(tuple: anytype) void {
    // 1. 获取类型信息
    // 这是一个编译期常量的 StructField 数组
    const fields = @typeInfo(@TypeOf(tuple)).@"struct".fields;

    // 2. 编译期循环展开
    // 必须用 inline for,因为每个字段的类型都可能不同
    inline for (fields) |field| {
        
        // 3. 打印详情
        // field.name 是字段名 (例如 "0")
        // field.type 是字段类型 (例如 bool)
        // @field(tuple, field.name) 取出实际的值
        print("\"{s}\"({any}):{any} ", .{
            field.name,
            field.type,
            @field(tuple, field.name),
        });
    }
}

核心知识点总结

1. 元组即结构体

t[0] 实际上是 t.@"0" 的语法糖。 这意味着元组和结构体在内存布局和使用方式上是高度统一的。你甚至可以给元组定义方法!

2. print 函数的原理

我们一直在写的 print("{}", .{a, b}),其实就是把 ab 打包成一个元组传入。 print 函数内部就是像我们写的 printTuple 一样,通过 inline for 遍历这个元组,对每个元素调用相应的格式化逻辑。

3. @field 的威力

在处理元组时,@field 尤为重要。因为我们不能写 tuple.i(i 是变量),但我们可以写 @field(tuple, field_name_string)。只要字段名字符串在编译期已知,我们就可以像访问数组一样访问结构体字段。


后续预告:恭喜!你已经完成了所有关于 Zig 核心语法特性的练习。接下来的旅程将完全不同。我们将离开舒适区,进入标准库 std 的实战演练。文件操作、哈希表、线程… 真正的系统编程才刚刚开始。