不可恢复错误 panic!
本教程共 78 篇 · 第 39 篇 · 更新于 2026-08-08 · 约 9 分钟阅读
本节目标:理解什么叫不可恢复错误,认识 panic 的被动触发与主动调用,会读 backtrace,并知道什么情况才该让程序崩溃。
错误分两种:一种能补救,比如文件没找到,重试或换路径就行;另一种严重到程序没法继续,比如系统启动时核心配置读不出来。后者就叫不可恢复错误,Rust 用 panic 来处理。
1-1 什么是 panic
panic 字面意思是”恐慌”。当 Rust 遇到它无法(或不该)继续下去的严重问题时,会触发 panic:打印错误信息、展开函数调用栈、然后退出程序。它和”温和地返回一个错误让你处理”完全不同——panic 是程序举手投降。
触发 panic 有两种方式:被动触发和主动调用。
1-2 被动触发:越界访问
最常见的是你写出 bug,Rust 替你 panic。比如数组越界:
fn main() {
let v = vec![1, 2, 3];
let _ = v[99]; // 只有 3 个元素,却访问第 100 个
}
运行后报错:
thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 99',
src/main.rs:4:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
为什么越界要直接崩,而不是像 C 那样”硬取一个值”?因为硬取可能拿到别的变量的内存,造成逻辑 bug,甚至被攻击者利用做缓冲区溢出。Rust 宁可当场崩溃、把位置告诉你,也不把隐患藏起来。这种”宁可明着死”的作风,反而让 bug 更好修。
1-3 主动调用 panic! 宏
有些场景你想自己喊停,比如系统启动阶段读关键文件失败。用 panic! 宏即可:
fn main() {
panic!("crash and burn");
}
输出会带上你写的信息和出错位置。
Warning只有”不知道该怎么处理、且后果严重”的错误,才用
panic!。千万别因为用户传了个非法参数就让整个程序崩掉——那种情况属于可恢复错误,下一章用Result处理。简单记:只有当你确信无法挽回时,才 panic。
1-4 读取 backtrace 栈回溯
报错信息最后一行提示加 RUST_BACKTRACE=1 能看到更详细的调用链。这对排查”错误经过了哪些函数”极有帮助:
# Linux / macOS
RUST_BACKTRACE=1 cargo run
# Windows (PowerShell)
$env:RUST_BACKTRACE=1 ; cargo run
开启后会打印从出错点一路回溯到 main 的调用顺序(最近调用的在最上面)。要拿到它,得在 Debug 模式(默认 cargo run/cargo build 就是)下编译。想看更全,用 RUST_BACKTRACE=full。
一个典型的回溯长这样(节选):
stack backtrace:
0: rust_begin_unwind
1: core::panicking::panic_fmt
2: core::panicking::panic_bounds_check
3: <usize as SliceIndex<[T]>>::index
4: core::slice::<impl Index for [T]>::index
5: <Vec<T> as Index>::index
6: world_hello::main
7: core::ops::function::FnOnce::call_once
读它的诀窍:从上往下看,最上面是 Rust 内部的处理函数,越往下越接近你的代码;main(第 6 行)基本是最初的调用入口。顺着这条链,你能一眼看出”错误经过了哪些函数”。不同操作系统、不同 Rust 版本的回溯细节会有差异,但”最近调用在最上”的规则不会变。
1-5 panic 的两种收尾方式
panic 发生后,程序怎么退场有两种选择:
- 栈展开(默认):从 panic 点往回,逐帧清理栈上的数据。善后更干净,报错信息更全,代价是多做些工作。
- 直接终止:不清理,立刻退出,善后交给操作系统。
绝大多数情况用默认就好。但如果你特别在意最终二进制文件的体积,可以在 Cargo.toml 的 release 配置里改用直接终止:
[profile.release]
panic = 'abort'
另外补充一点:如果是 main 线程 panic,整个程序终止;若是别的子线程 panic,只有那个线程挂掉,不影响主线程。所以重活尽量交给子线程,能避免一处崩溃拖垮全局。
1-6 何时该用 panic
配合下一章要讲的 Result<T, E>,Rust 提供了 unwrap 和 expect 这类”成功就取值、失败就 panic”的快捷方法:
use std::net::IpAddr;
let home: IpAddr = "127.0.0.1".parse().unwrap();
parse 返回 Result,unwrap 在成功时取出里面的值,失败时直接 panic。除了 unwrap,还有个更贴心的 expect:它允许你附上一句失败时打印的信息,排错时一眼就知道是哪一步崩的:
use std::net::IpAddr;
let home: IpAddr = "127.0.0.1".parse().expect("合法 IP 解析失败,检查输入");
expect 和 unwrap 行为完全一样,区别只在报错信息更友好。在原型里,优先用 expect 比裸 unwrap 更负责任——将来别人(或未来的你)看到崩溃,能马上定位。
适合在下面几种场景用:
示例、原型、测试。这几处要快速搭代码,错误处理会拖慢节奏,用 unwrap 最快。等正式要做错误处理时,全局搜这些方法替换掉即可。
你确切知道代码一定正确时。比如 "127.0.0.1" 铁定是合法 IP,parse 必成功,用 unwrap 让代码更干净。但若字符串来自用户输入,正式项目里就必须走 Result 错误处理,否则一天崩几十回。
可能导致全局有害状态时。比如启动流程出错、会影响后续所有代码,或者触及内存安全(数组越界就属于这一类)。这种错误不可预期、后果严重,应当 panic 而不是”处理后硬撑着跑”。
Tip一句话判断:错误可预期、能处理 → 用
Result;错误不可预期、会害全局 → 用panic。拿不准,优先走Result,把unwrap留给原型和测试。
两种路径怎么选,给你一张速查表:
| 场景 | 该用 |
|---|---|
| 文件不存在、用户输入非法、网络超时 | Result(可恢复) |
| 启动配置缺失、核心不变量被破坏 | panic(不可恢复) |
| 数组越界、空指针解引用 | panic(内存安全) |
| 示例 / 原型 / 测试里快速试错 | unwrap / expect |
记住一条主线:可预期、能补救的错,交给 Result;只有”继续跑也没意义”的错,才让程序崩溃。
1-7 panic 之后还能跑吗
一旦 panic! 触发,当前线程开始”展开(unwind)“:从出错点一层层往回退,依次运行已构造对象的析构函数,把栈清理干净。对于绝大多数程序,这意味着整个进程随后退出;但 Rust 也提供了 std::panic::catch_unwind,允许你在某一层”接住” panic,把它当作一个可恢复的值处理。
不过 catch_unwind 有不少限制:它只能接住”展开”式的 panic,如果你把 panic 策略设为 abort,或者 panic 发生在不安全的上下文中,它可能接不住。而且被接住的数据要满足 UnwindSafe 约束,新手很容易在这里踩到编译错误。所以它在写库、写测试框架时更有用,普通业务逻辑里别依赖它。
Warning在库代码中,尽量用
Result把错误交还给调用方,而不是随意panic!。突然终止会让使用者很难受;panic!更适合”程序继续下去也没有意义”的内部不变量被破坏场景。
1-8 panic 与测试:should_panic
panic! 虽然代表”不可恢复”,但在测试里它反而有用:有时你就是想验证”某段代码在错误输入下应当崩溃”。Rust 提供 #[should_panic] 属性来标注这类测试——如果里面的代码真的 panic 了,测试算通过;如果没崩,测试反而失败。
#[test]
#[should_panic]
fn test_index_out_of_bounds() {
let v = vec![1, 2, 3];
let _ = v[99]; // 越界,应当 panic
}
这说明 panic 不是”敌人”,它是 Rust 暴露”程序已无以为继”的信号。在写库时你不该随便 panic 打扰调用方;但在写测试、写不变量检查(比如”这个函数绝不该收到空切片”)时,主动 panic! 能及早暴露 bug。
Note真正发布给用户的程序里,尽量把可预期的错误用
Result交还给调用方;把panic!留给”按理说绝不会发生、一旦发生就是 Bug”的内部断言。
1-9 小结
- panic 是处理不可恢复错误的机制:打印信息、展开栈、退出程序;
- 越界等 bug 会被动触发 panic,这是 Rust 的安全保护;
- 主动用
panic!宏喊停,但只在无法挽回时; RUST_BACKTRACE=1看完整调用链,Debug 模式才有效;- 收尾默认栈展开,可配
panic = 'abort'直接终止; unwrap/expect是”失败就 panic”的快捷方式,适合原型、测试、确信正确的代码。
下一章看可恢复错误 Result,那是日常处理错误的主战场。