07
错误处理:panic、Result 与 ? 运算符
Error Handling · panic! · ? operator
Rust 把错误分成两类:不可恢复的用 panic!(直接崩溃),可恢复的用 Result<T, E>(返回给调用者处理)。没有受检异常那一套,全靠类型表达。
panic!:不可恢复错误
fn main() {
// panic! 直接让程序崩溃退出
// panic!("出大事了"); // 默认会"栈展开",一层层退栈并 drop
// 想看崩溃现场,设环境变量:
// RUST_BACKTRACE=1 cargo run
let v = vec![1,2,3];
println!("{}", v.get(10).unwrap_or(&-1)); // 越界不 panic,返回 None
}
论什么时候 panic
库函数别随便 panic——把错误返回给调用者;二进制主程序在确实"没法继续"时 panic 也合理。Cargo.toml 里 panic = "abort" 可以让崩溃直接中止而不展开,省二进制体积(嵌入式/Release 常用)。
? 运算符:错误传播的语法糖
use std::fs::File;
use std::io::{self, Read};
// 读文件全部内容:可能失败的地方,后面加一个 ?
fn read_username() -> Result<String, io::Error> {
let mut file = File::open("user.txt")?; // 失败就 return Err(...) 出去
let mut s = String::new();
file.read_to_string(&mut s)?; // 又一个 ?
Ok(s)
}
论? 到底干了啥
不用 ?,上面每个错误都得写一堆 match:match ... { Ok(v)=>v, Err(e)=>return Err(e) }。? 就是把这套样板自动帮你写了:是 Ok 就取里面的值继续,是 Err 就立刻 return。它还会自动用 From 把错误类型转成函数声明的错误类型,所以不同来源的错误能往一个 Result 里塞。? 在 Option 上也能用:let v = opt?; 是 None 就提前返回 None。
自定义错误类型与 thiserror / anyhow
// 手写:定义枚举错误 + impl Display + impl Error(啰嗦)
#[derive(Debug)]
enum MyError {
NotFound,
Io(io::Error),
}
// 生产里直接用 thiserror 派生,一行搞定(库 crate 用它)
// [dependencies]
// thiserror = "2"
// anyhow = "1"
/*
#[derive(thiserror::Error, Debug)]
enum MyErr {
#[error("用户 {0} 没找到")]
NotFound(String),
#[error(transparent)]
Io(#[from] std::io::Error), // #[from] 自动实现 From 转换
}
*/
fn main() {
println!("库用 thiserror 定义精确错误类型;二进制用 anyhow::Result 灵活兜底");
}
| 库 | 定位 | 用在哪 |
|---|---|---|
| thiserror | 帮你派生精确的自定义错误类型 | 写库时用——对外暴露清晰的错误变体,方便调用者 match |
| anyhow | 应用层灵活错误处理,anyhow::Result<T>,可加上下文 .context("...") | 写二进制应用时用——不用定义一堆错误类型,出问题打日志就够 |
生产环境别滥用 unwrap()
.unwrap() 遇到 Err/None 会直接 panic,把进程搞崩。示例代码里用用可以,生产代码要么 ? 传播、要么 match/map_err 妥善处理。.expect("为什么这里不该出错") 比 unwrap 多了一句说明,也是给 debug 看的,生产慎用。