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}),其实就是把 a 和 b 打包成一个元组传入。
print 函数内部就是像我们写的 printTuple 一样,通过 inline for 遍历这个元组,对每个元素调用相应的格式化逻辑。
3. @field 的威力
在处理元组时,@field 尤为重要。因为我们不能写 tuple.i(i 是变量),但我们可以写 @field(tuple, field_name_string)。只要字段名字符串在编译期已知,我们就可以像访问数组一样访问结构体字段。
后续预告:恭喜!你已经完成了所有关于 Zig 核心语法特性的练习。接下来的旅程将完全不同。我们将离开舒适区,进入标准库 std 的实战演练。文件操作、哈希表、线程… 真正的系统编程才刚刚开始。