Ziglings 笔记 25: 优雅的 Try

优雅的“甩锅”

在上一篇笔记中,我们为了把错误传递给上层函数,写了类似 catch |err| return err 这样略显繁琐的代码。

Ziglings 的第 25 个练习告诉我们:这种模式太常见了,以至于 Zig 专门为它设计了一个关键字——try

挑战:简化错误传播

我们有一个 addFive 函数,它调用 detect 函数。

  • detect 可能会报错。
  • 如果 detect 报错,addFive 也应该报错(把错误传出去)。
  • 如果 detect 成功,addFive 应该拿到数值并加 5。

解决方案

使用 try 关键字,一行代码搞定:

const std = @import("std");

const MyNumberError = error{
    TooSmall,
    TooBig,
};

pub fn main() void {
    // main 函数负责兜底,使用 catch 0 将错误处理为默认值
    const a: u32 = addFive(44) catch 0;
    const b: u32 = addFive(14) catch 0;
    const c: u32 = addFive(4) catch 0;

    std.debug.print("a={}, b={}, c={}\n", .{ a, b, c });
}

fn addFive(n: u32) MyNumberError!u32 {
    // 核心代码:try detect(n)
    // 意思是:尝试执行 detect(n)。
    // 1. 如果成功,把结果赋值给 x。
    // 2. 如果失败,立即 return 那个错误(不执行下一行)。
    const x = try detect(n);

    return x + 5;
}

fn detect(n: u32) MyNumberError!u32 {
    if (n < 10) return MyNumberError.TooSmall;
    if (n > 20) return MyNumberError.TooBig;
    return n;
}

核心知识点总结

1. try = catch |err| return err

这是 Zig 中最常用的语法糖之一。它极大地减少了代码量,让我们可以专注于“成功路径”的逻辑,而不需要像 Go 语言那样在每一行后面写 if err != nil { return err }

2. 提前返回 (Early Return)

try 遇到错误时,它会触发即时返回。这意味着 try 后面的代码(return x + 5)根本不会被执行。

3. 类型兼容性

要在一个函数内部使用 try,这个函数本身的返回类型必须能够容纳错误。 比如 addFive 返回的是 MyNumberError!u32,所以它完全有资格把 detect 产生的 MyNumberError 抛出去。


后续预告:到现在为止,我们的程序执行顺序都是线性的。但如果我想让某些代码(比如清理内存)等到函数结束前那一刻才执行,该怎么办?下一篇我们将学习 Zig 的延时执行机制 —— defer