Ziglings 笔记 24: 精准捕获与错误传递

不是所有的错误都一样

在上一篇笔记中,我们用 catch 提供了一个通用的默认值。但这就像是用一把大锤子,不管什么错误都一锤子砸扁。

Ziglings 的第 24 个练习展示了更精细的手术刀操作:Payload Capture (负载捕获)

挑战:责任链

在这个练习中,我们有一系列的函数,每个函数只负责修复一种特定的错误:

  • fixTooBig:只负责把太大的数改小。
  • fixTooSmall:只负责把太小的数改大。
  • detectProblems:负责发现问题。

我们需要实现 fixTooSmall,让它拦截 TooSmall 错误,但放行其他错误。

解决方案

这是实现的代码逻辑:

const std = @import("std");

const MyNumberError = error{
    TooSmall,
    TooBig,
};

// ... main 函数省略 ...

fn fixTooSmall(n: u32) MyNumberError!u32 {
    // 调用下层函数,并捕获可能的错误
    return detectProblems(n) catch |err| {
        // 检查:这是我能处理的错误吗?
        if (err == MyNumberError.TooSmall) {
            // 是的,我把太小的数字修正为 10
            return 10;
        }

        // 不是(比如是 TooBig),我处理不了
        // 原样返回错误,交给上层函数(fixTooBig)去处理
        return err;
    };
}

// ... 其他函数省略 ...

核心知识点总结

1. catch |variable|

这是 catch 的进阶形态。通过管道符 |err|,我们将错误值捕获到了变量中。这让我们可以在 catch 块内部使用 if (err == ...) 进行判断。

2. 错误的再抛出 (Rethrowing)

fixTooSmall 中,最关键的一行是最后的 return err;。 这意味着:“如果这个错误不是我能修的 TooSmall,那我就把它扔回去。” 正是这种机制,使得错误可以像泡泡一样,从底层的 detectProblems 一路向上冒,直到被某一层函数捕获并处理。

3. 手动 vs 自动

你可能会觉得写 catch |err| { return err; } 有点啰嗦。 如果我不想处理任何错误,只是想把它们全部向上传递呢?Zig 提供了一个极简的关键字来替代这种样板代码,那就是 try


后续预告:刚才我们手动写了错误传递的代码。下一篇,我们将学习 Zig 中最常用的关键字之一 try,看看它是如何将这几行代码简化为两个字符的。