Ziglings 笔记 22: 薛定谔的变量 (Error Unions)

要么成功,要么失败

在上一篇笔记中,我们定义了单纯的错误集。但在实际开发中,我们遇到的变量通常具有双重性:正常情况下它是数据,出问题时它是错误。

Ziglings 的第 22 个练习向我们展示了如何用 Error Union (错误联合) 来表达这种概念。

挑战:双重身份

代码声明了一个变量 my_number

  1. 一开始,它被赋值为整数 5
  2. 随后,它又被赋值为错误 MyNumberError.TooSmall

我们需要定义一个能够同时兼容这两种情况的数据类型。

解决方案

这是正确的类型定义:

const std = @import("std");

const MyNumberError = error{TooSmall};

pub fn main() void {
    // 关键语法:MyNumberError!u8
    // 读作:"MyNumberError OR u8"
    var my_number: MyNumberError!u8 = 5;

    // 现在它存储的是数字 5
    // ...

    // 现在它存储的是错误 TooSmall
    // 由于类型是用 '!' 连接的联合体,这种赋值是合法的
    my_number = MyNumberError.TooSmall;

    std.debug.print("I compiled!\n", .{});
}

核心知识点总结

1. 惊叹号 ! 的魔力

在 Zig 类型系统中,! 是一个二元操作符,用于创建 Error Union Type

  • 格式:[ErrorSet]![Type]
  • 例子:FileError!u32 表示这个值要么是 FileError 中的一种错误,要么是一个 u32 整数。

2. 内存效率

你可能会担心这种“联合类型”很占内存。 实际上,Zig 编译器对此做了极度的优化。在很多情况下,它不需要额外的内存来存储“这是错误还是值”的标签,而是利用了指针对齐或保留值的空隙。这使得 Zig 的错误处理比类似 Rust 的 Result<T, E> 往往更紧凑。

3. 这里的用法

虽然在这个练习中我们把变量定义为 MyNumberError!u8,但在实际的函数返回值中,我们经常简写为 !u8。这会让 Zig 自动推导所有可能的错误集,这被称为 Inferred Error Sets


后续预告:现在我们知道如何创建这种包含错误的变量了,但我们该如何使用它呢?如果它是个错误,程序会崩溃吗?下一篇我们将学习如何使用 catch 关键字来“拆包”这些盒子。