Ziglings 笔记 56: 完美的结合 (Tagged Unions)
自带说明书的盒子
在上一篇笔记中,为了区分 Insect 联合体里到底是蚂蚁还是蜜蜂,我们不得不笨拙地传递一个额外的枚举参数。如果传错了,程序就会读取错误的内存。
Ziglings 的第 56 个练习向我们展示了终极解决方案:Tagged Union (带标签的联合体)。
挑战:让联合体“自知”
我们需要修改 Insect 的定义,让它自己携带类型信息,并重写 printInsect 函数,利用 Switch 语句自动解包数据。
解决方案
这是利用 Tagged Union 优化后的代码:
const std = @import("std");
// 1. 定义标签枚举
const InsectStat = enum { flowers_visited, still_alive };
// 2. 定义 Tagged Union
// 语法:union(枚举名)
// 这相当于告诉编译器:请把 InsectStat 的值和数据存放在一起。
const Insect = union(InsectStat) {
flowers_visited: u16,
still_alive: bool,
};
pub fn main() void {
// 初始化时,Zig 会自动设置内部的标签
const ant = Insect{ .still_alive = true };
const bee = Insect{ .flowers_visited = 16 };
std.debug.print("Insect report! ", .{});
// 现在的函数调用非常简单,不需要传额外的枚举了
printInsect(ant);
printInsect(bee);
std.debug.print("\n", .{});
}
fn printInsect(insect: Insect) void {
// 3. 直接 Switch 联合体
// Zig 会自动检查内部的标签
switch (insect) {
// payload capture: |a|
// 如果是 .still_alive,把里面的 bool 值捕获给变量 a
.still_alive => |a| std.debug.print("Ant alive is: {}. ", .{a}),
// payload capture: |f|
// 如果是 .flowers_visited,把里面的 u16 值捕获给变量 f
.flowers_visited => |f| std.debug.print("Bee visited {} flowers. ", .{f}),
}
}
核心知识点总结
1. 什么是 Tagged Union?
它是一种数据结构,可以容纳不同类型的值,但在同一时间只能容纳其中一种。与 C 语言的 union 不同,Zig 的 Tagged Union 强制绑定了一个 enum 标签,用来记录当前存储的是哪种类型。这消除了“类型混淆”的风险。
2. 模式匹配 (Pattern Matching)
这个练习展示了 Zig 强大的模式匹配能力。
switch (insect) 实际上做了两件事:
- 控制流:根据标签跳转到正确的
case。 - 数据流:通过
|val|语法,安全地将内部数据解包(Unwrap)出来供我们使用。
3. 一通百通
回顾一下我们学过的:
- Optional (
?T):本质上就是union(enum) { null, value: T }。 - Error Union (
!T):本质上就是union(enum) { error_code, value: T }。
理解了 Tagged Union,你就理解了 Zig 类型系统的半壁江山。
后续预告:我们已经彻底掌握了基础数据类型。接下来,我们将再次回到 Switch 语句。我们之前只用了最基本的匹配,但如果我想匹配“某个范围”或者“多个值”呢?下一篇我们将学习 Switch 的进阶用法。