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 小时前
安全的自动更新怎么做:minisign 签名、先验签后解析与防回滚
rust·ssh
Delightrs19 小时前
Rust LeetCode238 算法题解
开发语言·算法·rust
奶油话梅糖19 小时前
快过 IDM 5 倍、开源免费:4,200 Star 的 Rust 下载器用 7 组对照实验“单挑“行业老大
rust·开源·idm
Zan1 天前
Tauri 2 实战:为什么一个可靠的桌面应用,应该由 Rust Host 管理生命周期?
前端·架构·rust
RobinDevNotes2 天前
从 Electron 到 GPUI,9 个跨平台桌面方案该怎么选
前端·javascript·rust·electron·openresty·前端开发工具
灿飞屋2 天前
Rust 编程其实很简单
开发语言·rust
蔡少将2 天前
用 Tauri 2 给 Mac 应用接 Touch ID,签名公证都做了,还是 -34018
rust
木子欢儿3 天前
国内 macOS/Linux 安装 rust
linux·运维·服务器·macos·rust
Kapaseker3 天前
Rust 一行 entry() 干掉五行 if/else
rust
红尘散仙3 天前
谁说终端只能敲键盘?我用 Rust 给小说阅读器加上了鼠标
前端·后端·rust