Rust panic 与 unwrap/expect:什么时候该崩
更新时间:2026-09-20。Rust 把错误分两类:可恢复用 Result(文件不存在、网络失败),不可恢复的程序员错误用 panic(越界、不变量被破坏)。分清这两类是写对 Rust 错误处理的关键。
一、什么会触发 panic
| 触发点 | 例子 |
|---|---|
| 数组/切片越界 | v[100] 而 len=3 |
Option::unwrap 遇到 None | None.unwrap() |
Result::unwrap 遇到 Err | Err(e).unwrap() |
整数除零(/、%) | 1 / 0 |
显式 panic!(...)、unreachable!()、todo!() | 断言失败 |
assert!/assert_eq! 失败 | 不变量破坏 |
panic 默认沿栈展开(unwind):运行各值的 Drop、打印位置和(设置后)调用栈;也可在 release 配置成直接 abort。
二、unwrap / expect / 安全替代
rust
let maybe: Option<i32> = Some(5);
let a = maybe.unwrap(); // None 就 panic
let b = maybe.expect("配置必须存在"); // panic 信息更明确,推荐于原型
let c = maybe.unwrap_or(0); // None 给默认值,不崩
let d = maybe.unwrap_or_else(|| calc());// 惰性默认值
let res: Result<i32,_> = "x".parse();
let n = res.unwrap_or_default(); // Err -> i32 默认 0选型原则:
expect("原因")优于裸unwrap():崩溃时能看到为什么这里「本不该失败」。- 只有当「这里在逻辑上绝不可能是 None/Err」(如常量字符串 parse 成整数、刚 insert 完立刻 get)时才用 unwrap/expect。
- 外部输入、IO、解析等可能失败的情况,用
match/?/默认值,不要让用户输入触发 panic。
三、拿到调用栈与定位
bash
RUST_BACKTRACE=1 ./myapp # 打印调用栈
RUST_BACKTRACE=full ./myapp # 更完整panic 信息含文件名、行号和原因,配 backtrace 能直接定位到出错的那次 unwrap/越界。
四、catch_unwind:只在边界兜底
rust
use std::panic;
let result = panic::catch_unwind(|| {
let v = vec![1,2,3]; v[10]
});
assert!(result.is_err()); // panic 被捕获为 Err(Box<dyn Any>)catch_unwind 不是常规错误处理(不能捕获 panic=abort,也拿不回普通错误信息),主要用于线程池/FFI/测试框架在任务边界隔离单个任务的崩溃,避免炸掉整个进程。普通业务请用 Result。
五、常见坑
- 把可恢复错误当 panic:见到
unwrap就一路点,线上坏数据直接崩;外部失败要用 Result +?。 - 裸
unwrap()无说明:崩了看不出预期,至少expect("…")。 - 以为 catch_unwind 能当 try/catch 用:它只是 unwind 边界,惯用法仍是 Result。
- 多线程里子线程 panic 被忽略:
JoinHandle::join返回 Err,不 join 就悄悄丢掉(见 std::thread)。 - release 配
panic=abort还依赖展开/catch_unwind:直接终止进程、无法捕获。
总结
panic 表示越界、unwrap None/Err、除零这类本不该发生的程序员错误,默认展开栈并打印位置、设 RUST_BACKTRACE 可看调用栈,而文件/网络等可恢复失败必须用 Result 与 ?;unwrap/expect 只用于逻辑上绝不可能失败处且 expect 要带原因,可能失败处用 unwrap_or/match/? 兜底,catch_unwind 仅在线程池、FFI 等崩溃隔离边界使用、不是通用异常机制,同时别忘记子线程 panic 要靠 join 的 Err 才能感知。