错误处理
Rust 将错误分为两类:可恢复错误(Result<T, E>)和不可恢复错误(panic!)。
panic! 宏
遇到不可恢复的错误时,程序会 panic:
fn main() {
panic!("程序崩溃了!");
}
何时使用 panic:
- 示例代码、原型
- 不可能发生的情况(逻辑错误)
- 库代码中的契约违反
Result 枚举
enum Result<T, E> {
Ok(T),
Err(E),
}
use std::fs::File;
fn main() {
let f = File::open("hello.txt");
let f = match f {
Ok(file) => file,
Err(error) => {
panic!("打开文件失败: {:?}", error);
}
};
}
匹配不同错误
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),
},
};
}
unwrap 和 expect
use std::fs::File;
fn main() {
// unwrap:Ok 返回值,Err 则 panic
let f = File::open("hello.txt").unwrap();
// expect:可以自定义 panic 消息
let f = File::open("hello.txt")
.expect("无法打开 hello.txt");
}
传播错误
使用 ? 运算符:
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")?; // 如果 Err,提前返回
let mut s = String::new();
f.read_to_string(&mut s)?;
Ok(s)
}
// 更简洁的链式调用
fn read_username_simplified() -> Result<String, io::Error> {
let mut s = String::new();
File::open("hello.txt")?.read_to_string(&mut s)?;
Ok(s)
}
Loading diagram...
? 只能用于返回 Result 的函数
fn main() -> Result<(), Box<dyn std::error::Error>> {
let f = File::open("hello.txt")?; // ✅ 现在可以在 main 中使用 ?
Ok(())
}
自定义错误类型
use std::fmt;
#[derive(Debug)]
enum MyError {
Io(std::io::Error),
Parse(std::num::ParseIntError),
}
impl fmt::Display for MyError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
MyError::Io(e) => write!(f, "IO 错误: {}", e),
MyError::Parse(e) => write!(f, "解析错误: {}", e),
}
}
}
impl std::error::Error for MyError {}
// 使用 From trait 自动转换
impl From<std::io::Error> for MyError {
fn from(err: std::io::Error) -> MyError {
MyError::Io(err)
}
}
Option vs Result
// Option:表示可能没有值
fn find_user(id: u32) -> Option<User> {
// ...
}
// Result:表示可能失败的操作
fn save_user(user: &User) -> Result<(), DatabaseError> {
// ...
}
and_then 和 map
fn main() {
let result: Result<i32, &str> = Ok(2);
// map:转换 Ok 中的值
let doubled = result.map(|x| x * 2); // Ok(4)
// and_then:链式调用可能失败的操作
let result = Ok(2)
.and_then(|x| Ok(x * 2))
.and_then(|x| Ok(x + 1)); // Ok(5)
}
实践建议
✅ 推荐做法
// 1. 使用 ? 传播错误
fn process() -> Result<(), MyError> {
let data = read_data()?;
transform(data)?;
Ok(())
}
// 2. 使用 expect 提供上下文
let config = load_config()
.expect("配置文件必须存在且格式正确");
// 3. 库代码返回 Result,应用代码处理错误
pub fn parse_number(s: &str) -> Result<i32, ParseError> {
s.parse().map_err(|e| ParseError::InvalidFormat(e))
}
❌ 避免做法
// ❌ 滥用 unwrap
let value = may_fail().unwrap(); // 可能导致 panic
// ❌ 忽略错误
let _ = may_fail(); // 错误被静默忽略
// ❌ 过度使用 panic
if x < 0 {
panic!("x 不能为负"); // 应该使用 Result
}
小结
- ✅
Result<T, E>用于可恢复错误 - ✅
panic!用于不可恢复错误 - ✅
?运算符简化错误传播 - ✅
unwrap和expect用于原型和测试 - ✅ 自定义错误类型提高可读性
- ✅ 库代码应返回
Result,让调用者处理