Rust 将错误分为两大类:可恢复错误和不可恢复错误。可恢复错误用 Result<T, E> 处理,不可恢复错误用 panic! 宏处理。Rust 没有异常机制,强制你在编译时就考虑错误处理。
1. panic! —— 不可恢复错误
1.1 触发 panic
1 2 3
| fn main() { panic!("crash and burn"); }
|
程序会打印错误信息、清理栈并退出。
1.2 panic 时的栈展开
默认情况下,panic 会展开栈并清理数据。可以在 Cargo.toml 中配置直接终止:
1 2
| [profile.release] panic = 'abort'
|
1.3 panic 回溯
设置环境变量查看详细回溯信息:
1
| RUST_BACKTRACE=1 cargo run
|
2. Result —— 可恢复错误
2.1 Result 枚举
1 2 3 4
| enum Result<T, E> { Ok(T), Err(E), }
|
2.2 使用 match 处理
1 2 3 4 5 6 7 8 9 10 11 12
| use std::fs::File;
fn main() { let f = File::open("hello.txt");
let f = match f { Ok(file) => file, Err(error) => { panic!("打开文件出错: {:?}", error); } }; }
|
2.3 匹配不同错误类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| use std::fs::File; use std::io::ErrorKind;
fn main() { let f = File::open("hello.txt");
let f = match f { Ok(file) => file, Err(error) => match error.kind() { ErrorKind::NotFound => match File::create("hello.txt") { Ok(fc) => fc, Err(e) => panic!("创建文件失败: {:?}", e), }, other_error => { panic!("打开文件出错: {:?}", other_error); } }, }; }
|
2.4 使用闭包简化
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| use std::fs::File; use std::io::ErrorKind;
fn main() { let f = File::open("hello.txt").unwrap_or_else(|error| { if error.kind() == ErrorKind::NotFound { File::create("hello.txt").unwrap_or_else(|error| { panic!("创建文件失败: {:?}", error); }) } else { panic!("打开文件出错: {:?}", error); } }); }
|
3. unwrap 和 expect
3.1 unwrap
unwrap 是 match 的快捷方式,成功返回值,失败则 panic:
1 2 3 4 5
| use std::fs::File;
fn main() { let f = File::open("hello.txt").unwrap(); }
|
3.2 expect
expect 类似 unwrap,但可以自定义错误信息:
1 2 3 4 5 6
| use std::fs::File;
fn main() { let f = File::open("hello.txt") .expect("无法打开 hello.txt"); }
|
推荐: 生产代码中优先使用 expect,提供清晰的上下文信息。
4. 传播错误
4.1 返回 Result
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| use std::fs::File; use std::io::{self, Read};
fn read_username_from_file() -> Result<String, io::Error> { let f = File::open("hello.txt");
let mut f = match f { Ok(file) => file, Err(e) => return Err(e), };
let mut s = String::new();
match f.read_to_string(&mut s) { Ok(_) => Ok(s), Err(e) => Err(e), } }
|
4.2 使用 ? 运算符
? 运算符是传播错误的简写:
1 2 3 4 5 6 7 8 9
| use std::fs::File; use std::io::{self, Read};
fn read_username_from_file() -> Result<String, io::Error> { let mut f = File::open("hello.txt")?; let mut s = String::new(); f.read_to_string(&mut s)?; Ok(s) }
|
进一步简化:
1 2 3 4 5 6 7 8
| use std::fs::File; use std::io::{self, Read};
fn read_username_from_file() -> Result<String, io::Error> { let mut s = String::new(); File::open("hello.txt")?.read_to_string(&mut s)?; Ok(s) }
|
最简洁版本:
1 2 3 4 5 6
| use std::fs; use std::io;
fn read_username_from_file() -> Result<String, io::Error> { fs::read_to_string("hello.txt") }
|
4.3 ? 运算符用于 Option
1 2 3
| fn last_char_of_first_line(text: &str) -> Option<char> { text.lines().next()?.chars().last() }
|
注意: ? 只能用在返回 Result 或 Option 的函数中。
4.4 main 函数返回 Result
1 2 3 4 5 6 7
| use std::error::Error; use std::fs::File;
fn main() -> Result<(), Box<dyn Error>> { let f = File::open("hello.txt")?; Ok(()) }
|
5. 何时使用 panic!
5.1 适合 panic 的场景
- 示例、原型代码:使用
unwrap 快速编写
- 测试代码:测试失败应该 panic
- 逻辑保证不会失败:编译器无法理解但你确定的情况
1 2 3 4 5
| use std::net::IpAddr;
let home: IpAddr = "127.0.0.1" .parse() .expect("硬编码的 IP 地址应该有效");
|
5.2 应该返回 Result 的场景
- 预期可能失败:如解析用户输入、网络请求
- 调用者需要决定如何处理:错误处理策略由调用方决定
5.3 违反约定时 panic
当代码接收到无效值时,应该 panic:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| pub struct Guess { value: i32, }
impl Guess { pub fn new(value: i32) -> Guess { if value < 1 || value > 100 { panic!("Guess 值必须在 1 到 100 之间,得到 {}", value); }
Guess { value } }
pub fn value(&self) -> i32 { self.value } }
|
6. 常用错误处理方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| let num = "abc".parse::<i32>().unwrap_or(0);
let num = "abc".parse::<i32>().unwrap_or_else(|_| 0);
let num = "abc".parse::<i32>().unwrap_or_default();
let result = File::open("file.txt") .and_then(|mut file| { let mut contents = String::new(); file.read_to_string(&mut contents)?; Ok(contents) });
|
总结
- panic!:用于不可恢复的错误,程序无法继续执行
- Result<T, E>:用于可恢复的错误,允许调用者决定如何处理
- ? 运算符:简化错误传播,使代码更简洁
- unwrap/expect:快速原型开发,生产代码优先使用
expect
Rust 的错误处理系统强制你在编译时考虑错误情况,虽然初期可能觉得繁琐,但能显著提高代码的健壮性和可靠性。
Hooray!错误处理学习完成!!!