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. 错误联合类型的解包

变量 ab 的类型是 u32,而不是 MyNumberError!u32。 这是因为 catch 运算符已经完成了解包 (Unwrapping) 的工作。它保证了无论发生什么,最终赋值给变量的一定是一个有效的 u32 整数,从而消除了后续代码处理错误的负担。

3. 返回类型的明确性

fn addTwenty(...) MyNumberError!u32 这种显式的类型签名让函数的行为一目了然。阅读代码的人一眼就能看出这个函数不仅会计算,还可能会失败,并且知道它失败时会抛出什么类型的错误。


后续预告:catch 适用于我们可以在当场解决错误的场景(比如用默认值)。但如果我们解决不了错误,想把它“甩锅”给调用者该怎么办?下一篇我们将学习 try 关键字。