Ziglings 笔记 23: 使用 Catch 提供默认值
永远要有 B 计划
在上一篇笔记中,我们学习了如何定义 Error Union(错误联合类型)。现在的问题是:当我们调用一个返回 !u32 的函数时,我们该怎么拿到里面的数字?
Ziglings 的第 23 个练习介绍了一个非常优雅的关键字:catch。
挑战:加法运算的保险丝
我们要编写一个函数 addTwenty,如果输入的数字太小(小于 5),它会报错;否则它返回 n + 20。
在主程序中,我们需要调用这个函数,并确保即使出错,程序也能拿到一个有效的数字(默认值)。
解决方案
这是完善后的代码:
const std = @import("std");
const MyNumberError = error{TooSmall};
pub fn main() void {
// 情况 1: 正常执行
// addTwenty(44) 返回 64,catch 不会被触发
const a: u32 = addTwenty(44) catch 22;
// 情况 2: 发生错误
// addTwenty(4) 返回 error.TooSmall
// catch 捕获到错误,并将其替换为默认值 22
const b: u32 = addTwenty(4) catch 22;
std.debug.print("a={}, b={}\n", .{ a, b });
}
// 核心签名:MyNumberError!u32
// 表示:可能返回 MyNumberError 错误,也可能返回 u32 整数
fn addTwenty(n: u32) MyNumberError!u32 {
if (n < 5) {
return MyNumberError.TooSmall;
} else {
return n + 20;
}
}
核心知识点总结
1. catch 就像“如果失败则…”
catch 是处理 Zig 错误最简单的方法之一。它的逻辑非常直白:尝试执行左边的表达式,如果成功就用结果;如果失败(返回错误),就使用右边的值。
这消除了像 Java 那样繁琐的 try-catch 块,让一行代码就能完成“尝试并提供默认值”的操作。
2. 错误联合类型的解包
变量 a 和 b 的类型是 u32,而不是 MyNumberError!u32。
这是因为 catch 运算符已经完成了解包 (Unwrapping) 的工作。它保证了无论发生什么,最终赋值给变量的一定是一个有效的 u32 整数,从而消除了后续代码处理错误的负担。
3. 返回类型的明确性
fn addTwenty(...) MyNumberError!u32
这种显式的类型签名让函数的行为一目了然。阅读代码的人一眼就能看出这个函数不仅会计算,还可能会失败,并且知道它失败时会抛出什么类型的错误。
后续预告:catch 适用于我们可以在当场解决错误的场景(比如用默认值)。但如果我们解决不了错误,想把它“甩锅”给调用者该怎么办?下一篇我们将学习 try 关键字。