恐慌的不可恢复的错误!


有时,您的代码中会发生坏事,而您对此无能为力。在这些情况下,Rust 具有 panic! 宏。在实践中,有两种方法可以引起 panic:采取导致代码 panic 的作(例如访问超过末尾的数组)或显式调用 panic!宏。在这两种情况下,我们都会在我们的程序中引起恐慌。默认情况下,这些 panic 将打印失败消息、展开、清理堆栈和 quit。通过环境变量,你还可以让 Rust 在发生 panic 时显示调用堆栈,以便更容易追踪 panic 的来源。


展开堆栈或中止以响应 panic


默认情况下,当 panic 发生时,程序开始展开,这意味着 Rust 会回到堆栈中并清理它遇到的每个函数的数据。然而,走回去清理是一项艰巨的工作。因此,Rust 允许您选择立即中止的替代方案,这将结束程序而不进行清理。


然后,作系统需要清理程序使用的内存。如果你的项目需要使生成的二进制文件尽可能小,你可以通过在 panic 中将 panic = 'abort' 添加到适当的 [profile] 部分来从展开切换到中止。 Cargo.toml 文件。例如,如果你想在 release 模式下在 panic 时中止,请添加以下内容:

[profile.release]
panic = 'abort'


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


文件名: src/main.rs

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 消息和源代码中发生 panic 的位置:src/main.rs:2:5 表示它是 src/main.rs 文件的第二行,第五个字符。


在这种情况下,指示的行是我们代码的一部分,如果我们转到该行,我们会看到 panic!宏调用。在其他情况下,panic! 调用可能在我们的代码调用的代码中,错误消息报告的文件名和行号将是调用 panic! 宏的其他人的代码,而不是最终导致 panic! 调用的代码行。


我们可以使用 panic! 调用的函数的回溯来找出导致问题的代码部分。为了理解如何使用 panic! 回溯,让我们看另一个例子,看看当我们的代码中有 bug 而来自 panic! 调用时是什么感觉,而不是来自我们直接调用宏的代码。示例 9-1 有一些代码试图访问超出有效索引范围的 vector 中的索引。


文件名: src/main.rs

fn main() {
    let v = vec![1, 2, 3];

    v[99];
}


示例 9-1:尝试访问 vector 末尾之外的元素,这将导致调用 panic!


在这里,我们尝试访问向量的第 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.


note: 行告诉我们可以设置 RUST_BACKTRACE 环境 变量来回溯导致错误的确切原因。一个 Backtrace 是为到达此点而调用的所有函数的列表。Rust 中的回溯与其他语言一样工作:读取回溯的关键是从顶部开始读取,直到看到你写的文件。这就是问题的根源。该 spot 上方的行是您的代码已调用的代码;以下几行是调用您的代码的代码。这些前后行可能包括核心 Rust 代码、标准库代码或您正在使用的 crate。让我们尝试通过将 RUST_BACKTRACE 环境变量设置为除 0 之外的任何值来获取回溯。示例 9-2 显示了与你将看到的类似的输出。

$ 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/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/std/src/panicking.rs:645:5
   1: core::panicking::panic_fmt
             at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/core/src/panicking.rs:72:14
   2: core::panicking::panic_bounds_check
             at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/core/src/panicking.rs:208:5
   3: <usize as core::slice::index::SliceIndex<[T]>>::index
             at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/core/src/slice/index.rs:255:10
   4: core::slice::index::<impl core::ops::index::Index<I> for [T]>::index
             at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/core/src/slice/index.rs:18:9
   5: <alloc::vec::Vec<T,A> as core::ops::index::Index<I>>::index
             at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/alloc/src/vec/mod.rs:2770:9
   6: panic::main
             at ./src/main.rs:4:6
   7: core::ops::function::FnOnce::call_once
             at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/core/src/ops/function.rs:250:5
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.


示例 9-2:调用 恐慌!设置环境变量 RUST_BACKTRACE 时显示


这是很大的产出!您看到的确切输出可能会有所不同,具体取决于您的作系统和 Rust 版本。为了获取包含此信息的回溯,必须启用 debug symbols。当使用不带 --release 标志的 cargo buildcargo run 时,调试符号默认启用,就像我们在这里一样。


在示例 9-2 的输出中,回溯的第 6 行指向我们项目中导致问题的行:src/main.rs 的第 4 行。如果我们不希望我们的程序出现恐慌,我们应该从第一行提到我们编写的文件所指向的位置开始调查。在示例 9-1 中,我们故意编写了会 panic 的代码,解决 panic 的方法是不要请求超出 vector 索引范围的元素。当你的代码将来出现 panic 时,你需要弄清楚代码正在采取什么作和哪些值会导致 panic 以及代码应该做什么。


我们将返回 panic!,以及何时我们应该和不应该使用 panic! 来处理 “To panic!或不 恐慌!部分。接下来,我们将了解如何使用 Result 从错误中恢复。