Rust 学习笔记 13:错误处理 (Error Handling)
“To err is human; to handle it safely is Rusty.”
Go 开发者最熟悉的代码可能是 if err != nil。
Rust 也有类似的机制,但它利用强大的枚举类型 Result<T, E> 把错误处理提升到了一个新的高度。
Rust 将错误分为两类:
- 不可恢复错误:bug 导致的,比如数组越界。使用
panic!。 - 可恢复错误:文件未找到、网络超时等。使用
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 零成本抽象的关键。
练习题:
- 修改
main.rs中的read_username_from_file_short函数,使其能处理文件不存在的错误(不要 panic,从函数返回)。 - 尝试编写一个函数,接受一个字符串,将其解析为
i32。如果解析失败,不要 panic,而是返回一个默认值 0。
思考题:
Go 的 defer 关键字用于资源清理(如关闭文件)。Rust 没有 defer,那它是怎么保证文件句柄在出错提前返回时被关闭的?(提示:RAII 和 Drop Trait)。
本文代码示例:
关注公众号:极客老墨
更多 AI 应用开发、工程实践和效率工具分享,欢迎扫码关注。
