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,看看它是如何将这几行代码简化为两个字符的。