9.1无法恢复的错误与 panic!

无法恢复的错误与 panic!

有时你的代码中会发生严重问题,而你无能为力。在这些情况下,Rust 提供了 panic! 宏。实际上,有两种方式会导致 panic:一种是执行某个操作使代码产生 panic(例如访问数组越界),另一种是显式调用 panic! 宏。无论哪种情况,都会在程序中引发 panic。默认情况下,这些 panic 会打印失败信息、展开栈帧、清理堆栈并退出程序。通过设置环境变量,你还可以让 Rust 在发生 panic 时显示调用栈,以便更容易定位问题源头。

响应 Panic 的栈展开或终止

默认情况下,当发生 panic 时,程序开始进行栈展开,也就是 Rust 会沿着调用链向上回溯,并清理每个函数中的数据。然而,回溯和清理工作量较大。因此,Rust 允许你选择立即终止程序作为替代方案,即不进行任何清理直接结束运行。

此时程序占用的内存将由操作系统负责回收。如果你的项目需要尽可能减小生成的二进制文件体积,可以通过在 Cargo.toml 文件相应的 profile 部分添加 panic = 'abort' 来切换为遇到 panic 时直接终止。例如,如果想要在发布模式下遇到panic就终止,可以这样写:

toml 复制代码
[profile.release]
panic = 'abort'

下面我们尝试在一个简单程序中调用 panic!

文件名:src/main.rs

rust 复制代码
fn main() {
    panic!("crash and burn");
}

运行该程序,会看到类似如下输出:

复制代码
$ cargo run
   Compiling panic v0.1.0 (file:///projects/panic)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.25s
     Running `target/debug/panic`

thread 'main' panicked at src/main.rs:2:5:
crash and burn
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

panic! 的调用导致最后两行显示错误信息。第一行展示了我们的panic消息以及出错位置:src/main.rs:2:5 表示这是 src/main.rs 文件第 2 行,第 5 个字符处。

这里指示的位置属于我们的代码,如果跳转至该行,就能看到触发宏调用的那条语句。但有时候,panic! 调用可能是在被我们代码间接调用的其他库代码里,此时错误消息报告的是那个库文件及其对应行号,而非最终导致该宏被触发的我们自己的源码位置。

我们可以利用产生这个panic!调用函数链上的回溯来找出引起问题的具体部分。为了理解如何使用这种backtrace,我们来看另一个例子,当因为我们的bug而从库内部触发了一个panic!而不是直接从自己代码里调动宏时,会是什么样子。列表9-1包含了一段尝试访问 vector 中超出有效索引范围元素的代码示例。

这里,我们试图访问向量的第100个元素(索引为99,因为索引从0开始),但该向量只有三个元素。在这种情况下,Rust 会发生 panic。使用 \[\] 应该返回一个元素,但如果传入了无效的索引,Rust 无法返回正确的元素。在 C 语言中,尝试读取数据结构末尾之外的数据是未定义行为。你可能会得到内存中对应那个位置的数据,即使那块内存不属于该数据结构。这被称为缓冲区溢出读取,如果攻击者能够操纵索引以读取他们不应该访问的数据,就可能导致安全漏洞。为了保护程序免受此类漏洞影响,如果你尝试读取不存在的索引处的元素,Rust 会停止执行并拒绝继续运行。我们来试试看:

复制代码
$ cargo run
Compiling panic v0.1.0 (file:///projects/panic)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.27s
Running `target/debug/panic`
thread 'main' panicked at src/main.rs:4:6: index out of bounds: the len is 3 but the index is 99
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

这个错误指向 main.rs 的第4行,我们在这里尝试访问向量 v 中的索引99。

note 行告诉我们可以设置 RUST_BACKTRACE 环境变量来获取导致错误发生时的回溯信息。回溯是一系列调用函数列表,用于追踪到达当前点所经过的所有函数调用。Rust 中的回溯与其他语言类似:阅读回溯时应从顶部开始,一直读到看到自己编写文件的位置,那就是问题起源所在的位置。上方代码是你的代码调用过的,下方代码则是调用你的代码。这些上下文可能包括 Rust 核心代码、标准库或你使用的一些 crate。

让我们通过将 RUST_BACKTRACE 环境变量设置为非零值来获取回溯,如下所示:

复制代码
$ RUST_BACKTRACE=1 cargo run
thread 'main' panicked at src/main.rs:4:6: index out of bounds: the len is 3 but the index is 99
stack backtrace:
0: rust_begin_unwind at /rustc/4d91de4e48198da2e33413efdcd9cd2cc0c46688/library/std/src/panicking.rs:692:5
1: core::panicking::panic_fmt at /rustc/4d91de4e48198da2e33413efdcd9cd2cc0c46688/library/core/src/panicking.rs:75:14
2: core::panicking::panic_bounds_check at /rustc/4d91de4e48198da2e33413efdcd9cd2cc0c46688/library/core/src/panicking.rs:273:5
3:<usize as core::slice::index::SliceIndex<[T]>>::index at file:///home/.rustup/toolchains/1.85/lib/rustlib/src/rust/library/core/src/slice/index.rs :274 :10
4:core :: slice :: index::<impl core :: ops :: index :: Index<I> for [T]>::index 在 file:///home/.rustup/toolchains/1.85/lib/rustlib/src/rust/library/core/src/slice/index .rs :16 :9
...
note:部分细节已省略,可通过设置 `RUST_BACKTRACE=full` 获取详细完整回溯。

清单9-2:当环境变量 RUST_BACKTRACE 设置后,由 panic! 调用生成并显示出的回溯信息

输出内容非常多!具体输出可能因操作系统和 Rust版本不同而异。要获得带有这些信息的回溯,需要启用调试符号。当使用 cargo build 或 cargo run 且未加 --release 标志时,会默认启用调试符号,这正是我们的情况。

在清单9-2中的输出里,第6行指向项目中导致问题的位置------src/main.rs 文件第4行。如果不希望程序 panic,应从第一条提及自己编写文件路径的信息开始调查。在清单9-1中,我们故意写了会触发 panic 的代码,要修复它,只需避免请求超出向量范围之外的元素即可。当未来你的代码出现 panic 时,你需要弄明白是什么操作、什么数值导致了 panic,以及应该如何改正这段逻辑。

我们将在"是否应该使用 panic!"章节中进一步讨论何时以及为何应或不应使用 panic! 来处理错误情况。

相关推荐
传奇开心果编程16 小时前
【Xilem 0.4 基础语法学与练】第一课:从零到计数器
学习·rust·前端框架
Flynt1 天前
pnpm 12 换上了 Rust 内核,我拿项目实测了一轮构建速度
rust·vite·前端工程化
传奇开心果编程1 天前
【Xilem基础语法学与练】第8课:条件渲染(one_of)
学习·rust·前端框架
qwsaedca1 天前
在Mac上跑 Kokoro TTS经验总结
rust·mac·tts
qwsaedca1 天前
Kokoro TTS v1.1 voices 文件格式逆向分析
rust
老猿讲编程1 天前
【Eclipse OpenSOVD学习之五】拓扑引擎(Topology)
学习·rust·eclipse·sovd
object not found1 天前
Nuxt4去掉body中默认的边距
开发语言·后端·rust
梦醒沉醉2 天前
4、Rust参考手册——Crate和源文件
rust
chainbees2 天前
Windows 系统 Rust 运行环境搭建
rust