Rust 学习笔记 13:错误处理 (Error Handling)

“To err is human; to handle it safely is Rusty.”

Go 开发者最熟悉的代码可能是 if err != nil。 Rust 也有类似的机制,但它利用强大的枚举类型 Result<T, E> 把错误处理提升到了一个新的高度。

Rust 将错误分为两类:

  1. 不可恢复错误:bug 导致的,比如数组越界。使用 panic!
  2. 可恢复错误:文件未找到、网络超时等。使用 Result<T, E>

1. Panic! (不可恢复错误)

当程序遇到无法处理的状况时(例如访问数组越界),Rust 会 panic。程序会打印错误信息,展开 (unwind) 栈,清理数据,然后退出。

1panic!("crash and burn");

你也可以配置 Cargo.toml 让 panic 时直接 abort(不清理栈),以减小二进制体积。

2. Result (可恢复错误)

大部分时候我们不希望程序崩溃。

1enum Result<T, E> {
2    Ok(T),
3    Err(E),
4}

处理 Result 的标准姿势是 match

1let f = File::open("hello.txt");
2
3let f = match f {
4    Ok(file) => file,
5    Err(error) => panic!("Problem opening the file: {:?}", error),
6};

unwrap 和 expect: 如果你确定不会出错,或者为了省事(在 demo 代码中),可以使用 unwrap()。如果出错它会直接 panic。expect("msg") 则是带自定义消息的 unwrap。

3. 传播错误 (Propagating Errors)

当你写一个函数,自己处理不了错误,想抛给调用者时,在 Go 里是 return nil, err。 在 Rust 里,早期也是写 match 然后 return Err(e)。 现在有了 ? 运算符

1fn read_username_from_file() -> Result<String, io::Error> {
2    let mut f = File::open("hello.txt")?; // 出错直接 return Err
3    let mut s = String::new();
4    f.read_to_string(&mut s)?; // 出错直接 return Err
5    Ok(s)
6}

甚至可以链式调用: File::open("hello.txt")?.read_to_string(&mut s)?;

这比 Go 的 if err != nil 少写了太多代码,而且逻辑清晰。

4. 什么时候 Panic,什么时候 Result?

  • 示例、原型、测试:用 unwrap / expect / panic! 没问题。
  • 库代码:绝对不要 panic(除非遇到违反约定的 bug),永远只返回 Result。让调用者决定是 panic 还是处理。
  • 验证:如果通过类型系统无法保证约束(比如解析字符串为 IP 地址),可以用 panic 里的 assert! 宏来防御性编程。

5. 小结

第十三篇笔记。

  • Panic 炸掉程序。
  • Result 优雅地传递错误。
  • ? 运算符是处理 Result 的神器。

掌握了错误处理,你的 Rust 程序就有了"健壮性"的基石。

下一篇,我们将进入 Rust 的深水区之一:泛型 (Generics)。Go 1.18 才引入泛型,而 Rust 从第一天起就拥抱泛型,它是 Rust 零成本抽象的关键。


练习题

  1. 修改 main.rs 中的 read_username_from_file_short 函数,使其能处理文件不存在的错误(不要 panic,从函数返回)。
  2. 尝试编写一个函数,接受一个字符串,将其解析为 i32。如果解析失败,不要 panic,而是返回一个默认值 0。

思考题

Go 的 defer 关键字用于资源清理(如关闭文件)。Rust 没有 defer,那它是怎么保证文件句柄在出错提前返回时被关闭的?(提示:RAII 和 Drop Trait)。


本文代码示例

关注公众号:极客老墨

更多 AI 应用开发、工程实践和效率工具分享,欢迎扫码关注。

极客老墨微信公众号二维码

相关阅读