Ziglings 笔记 33: If 也能处理错误

分岔路口

在之前的笔记中,我们使用了 try(直接甩锅)和 catch(提供备胎)来处理错误。但在 Ziglings 的第 33 个练习中,我们遇到了一种更细腻的处理方式。

有时候,我们需要在成功时使用那个值,而在失败时根据具体的错误原因做不同的事情。这时,If 错误处理表达式就派上用场了。

挑战:精准报错

代码要求我们遍历一组数字:

  1. 如果数字正常(等于 4),打印它。
  2. 如果数字不正常(返回错误),我们需要判断是太大了还是太小了,并打印相应的提示。

解决方案

这是使用 if-else 配合 switch 的实现:

const std = @import("std");

const MyNumberError = error{
    TooBig,
    TooSmall,
};

pub fn main() void {
    const nums = [_]u8{ 2, 3, 4, 5, 6 };

    for (nums) |num| {
        std.debug.print("{}", .{num});

        const n = numberMaybeFail(num);
        
        // 核心语法:if (result) |value| ... else |err|
        // 这是一个专门用于处理 Error Union 的 if 变体
        if (n) |value| {
            // 成功路径:value 是解包后的 u8
            std.debug.print("={}. ", .{value});
        } else |err| switch (err) {
            // 失败路径:err 是捕获到的错误
            // 我们使用 switch 来区分具体的错误类型
            MyNumberError.TooBig => std.debug.print(">4. ", .{}),
            
            // 补充缺失的 Case:
            MyNumberError.TooSmall => std.debug.print("<4. ", .{}),
        }
    }

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

fn numberMaybeFail(n: u8) MyNumberError!u8 {
    if (n > 4) return MyNumberError.TooBig;
    if (n < 4) return MyNumberError.TooSmall;
    return n;
}

核心知识点总结

1. if 的多面性

我们之前学过 if (bool),现在我们看到了 if (error_union)。 Zig 复用了 if 关键字来处理完全不同的场景,这减少了关键字的数量。编译器会根据括号里变量的类型自动判断这是普通的条件判断,还是错误解包。

2. 负载捕获 (Payload Capture)

这里的 |value||err| 再次展示了 Zig 的捕获语法。

  • 成功块中,我们拿到了安全可用的数据。
  • 失败块中,我们拿到了错误对象。 这种将“控制流”和“数据解包”结合在一起的设计,避免了像 C 语言那样先检查 err != 0 再去读数据的分离感。

3. 何时使用?

  • 如果你想失败时直接退出 -> 用 try
  • 如果你想失败时给个默认值 -> 用 catch
  • 如果你想失败时打印日志或根据错误类型做不同补救 -> 用 if (...) else |err|

后续预告:我们已经彻底掌握了 Zig 的基本控制流和错误处理。接下来,我们要迎来第四次阶段性测验 (Quiz 4),它将综合考察我们的实战能力。