错误处理 - panic! 与不可恢复的错误
突然有一天,糟糕的事情发生了,而你对此束手无策。对于这种情况,Rust 有 panic!
宏。当执行这个宏时,程序会打印出一个错误信息,展开并清理栈数据,然后接着退出。出现这种情况的场景通常是检测到一些类型的 bug 而且程序员并不清楚该如何处理它。
当出现
panic!
时,程序默认会开始 展开(unwinding),这意味着 Rust 会回溯栈并清理它遇到的每一个函数的数据,不过这个回溯并清理的过程有很多工作。另一种选择是直接 终止(abort),这会不清理数据就退出程序。那么程序所使用的内存需要由操作系统来清理。如果你需要项目的最终二进制文件越小越好,可以由 panic 时展开切换为终止,通过在 Cargo.toml 的[profile]
部分增加panic = 'abort'
。例如,如果你想要在发布模式中 panic 时直接终止:
让我们在一个简单的程序中调用 panic!
:
文件名: src/main.rs
fn main() {
panic!("crash and burn");
}
运行程序将会出现类似这样的输出:
在这个例子中,被指明的那一行是我们代码的一部分,而且查看这一行的话就会发现 panic!
宏的调用。在其他情况下,panic!
可能会出现在我们的代码调用的代码中。错误信息报告的文件名和行号可能指向别人代码中的 panic!
宏调用,而不是我们代码中最终导致 panic!
的那一行。可以使用 panic!
被调用的函数的 backtrace 来寻找(我们代码中出问题的地方)。下面我们会详细介绍 backtrace 是什么。
使用 panic!
的 backtrace
让我们来看看另一个因为我们代码中的 bug 引起的别的库中 panic!
的例子,而不是直接的宏调用。示例 9-1 有一些尝试通过索引访问 vector 中元素的例子:
文件名: src/main.rs
fn main() {
let v = vec![1, 2, 3];
v[99];
}
示例 9-1:尝试访问超越 vector 结尾的元素,这会造成 panic!
这里尝试访问 vector 的第一百个元素,不过它只有三个元素。这种情况下 Rust 会 panic。[]
应当返回一个元素,不过如果传递了一个无效索引,就没有可供 Rust 返回的正确的元素。
为了使程序远离这类漏洞,如果尝试读取一个索引不存在的元素,Rust 会停止执行并拒绝继续。尝试运行上面的程序会出现如下:
这指向了一个不是我们编写的文件,libcollections/vec.rs。这是标准库中 的实现。这是当对 vector v
使用 []
时 libcollections/vec.rs 中会执行的代码,也是真正出现 panic!
的地方。
接下来的几行提醒我们可以设置 RUST_BACKTRACE
环境变量来得到一个 backtrace backtrace 是一个执行到目前位置所有被调用的函数的列表。Rust 的 backtrace 跟其他语言中的一样:阅读 backtrace 的关键是从头开始读直到发现你编写的文件。这就是问题的发源地。这一行往上是你的代码调用的代码;往下则是调用你的代码的代码。这些行可能包含核心 Rust 代码,标准库代码或用到的 crate 代码。让我们尝试获取一个 backtrace:示例 9-2 展示了与你看到类似的输出:
$ RUST_BACKTRACE=1 cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
Running `target/debug/panic`
thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 99', /checkout/src/liballoc/vec.rs:1555:10
stack backtrace:
at /checkout/src/libstd/sys/unix/backtrace/tracing/gcc_s.rs:49
1: std::sys_common::backtrace::_print
at /checkout/src/libstd/sys_common/backtrace.rs:71
2: std::panicking::default_hook::{{closure}}
at /checkout/src/libstd/sys_common/backtrace.rs:60
at /checkout/src/libstd/panicking.rs:381
3: std::panicking::default_hook
at /checkout/src/libstd/panicking.rs:397
4: std::panicking::rust_panic_with_hook
at /checkout/src/libstd/panicking.rs:611
5: std::panicking::begin_panic
at /checkout/src/libstd/panicking.rs:572
at /checkout/src/libstd/panicking.rs:522
7: rust_begin_unwind
8: core::panicking::panic_fmt
at /checkout/src/libcore/panicking.rs:71
9: core::panicking::panic_bounds_check
at /checkout/src/libcore/panicking.rs:58
10: <alloc::vec::Vec<T> as core::ops::index::Index<usize>>::index
at /checkout/src/liballoc/vec.rs:1555
11: panic::main
at src/main.rs:4
12: __rust_maybe_catch_panic
at /checkout/src/libpanic_unwind/lib.rs:99
13: std::rt::lang_start
at /checkout/src/libstd/panicking.rs:459
at /checkout/src/libstd/panic.rs:361
at /checkout/src/libstd/rt.rs:61
14: main
16: <unknown>
示例 9-2:当设置 RUST_BACKTRACE
环境变量时 panic!
调用所生成的 backtrace 信息
这里有大量的输出!你实际看到的输出可能因不同的操作系统和 Rust 版本而有所不同。为了获取带有这些信息的 backtrace,必须启用 debug 标识。当不使用 —release 参数运行 cargo build 或 cargo run 时 debug 标识会默认启用,这里便是如此。
本章的后面会再次回到 并讲到何时应该及何时不应该使用这个方式。接下来,我们来看看如何使用 Result
来从错误中恢复。