首页 / Rust 入门教程 / 不可恢复错误 panic!

Rust 入门教程

不可恢复错误 panic!

本教程共 78 篇 · 第 39 篇 · 更新于 2026-08-08 · 约 9 分钟阅读

RustRust 入门教程panic不可恢复错误backtraceunwrap栈展开

本节目标:理解什么叫不可恢复错误,认识 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.tomlrelease 配置里改用直接终止:

[profile.release]
panic = 'abort'

另外补充一点:如果是 main 线程 panic,整个程序终止;若是别的子线程 panic,只有那个线程挂掉,不影响主线程。所以重活尽量交给子线程,能避免一处崩溃拖垮全局。

1-6 何时该用 panic

配合下一章要讲的 Result<T, E>,Rust 提供了 unwrapexpect 这类”成功就取值、失败就 panic”的快捷方法:

use std::net::IpAddr;
let home: IpAddr = "127.0.0.1".parse().unwrap();

parse 返回 Resultunwrap 在成功时取出里面的值,失败时直接 panic。除了 unwrap,还有个更贴心的 expect:它允许你附上一句失败时打印的信息,排错时一眼就知道是哪一步崩的:

use std::net::IpAddr;
let home: IpAddr = "127.0.0.1".parse().expect("合法 IP 解析失败,检查输入");

expectunwrap 行为完全一样,区别只在报错信息更友好。在原型里,优先用 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,那是日常处理错误的主战场。