错误处理 - panic! 与不可恢复的错误

优质
小牛编辑
113浏览
2023-12-01

突然有一天,糟糕的事情发生了,而你对此束手无策。对于这种情况,Rust 有 panic!宏。当执行这个宏时,程序会打印出一个错误信息,展开并清理栈数据,然后接着退出。出现这种情况的场景通常是检测到一些类型的 bug 而且程序员并不清楚该如何处理它。

当出现 panic! 时,程序默认会开始 展开unwinding),这意味着 Rust 会回溯栈并清理它遇到的每一个函数的数据,不过这个回溯并清理的过程有很多工作。另一种选择是直接 终止abort),这会不清理数据就退出程序。那么程序所使用的内存需要由操作系统来清理。如果你需要项目的最终二进制文件越小越好,可以由 panic 时展开切换为终止,通过在 Cargo.toml[profile] 部分增加 panic = 'abort'。例如,如果你想要在发布模式中 panic 时直接终止:

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

文件名: src/main.rs

  1. fn main() {
  2. panic!("crash and burn");
  3. }

运行程序将会出现类似这样的输出:

在这个例子中,被指明的那一行是我们代码的一部分,而且查看这一行的话就会发现 panic! 宏的调用。在其他情况下,panic! 可能会出现在我们的代码调用的代码中。错误信息报告的文件名和行号可能指向别人代码中的 panic! 宏调用,而不是我们代码中最终导致 panic! 的那一行。可以使用 panic! 被调用的函数的 backtrace 来寻找(我们代码中出问题的地方)。下面我们会详细介绍 backtrace 是什么。

使用 panic! 的 backtrace

让我们来看看另一个因为我们代码中的 bug 引起的别的库中 panic! 的例子,而不是直接的宏调用。示例 9-1 有一些尝试通过索引访问 vector 中元素的例子:

文件名: src/main.rs

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

示例 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 展示了与你看到类似的输出:

  1. $ RUST_BACKTRACE=1 cargo run
  2. Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
  3. Running `target/debug/panic`
  4. thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 99', /checkout/src/liballoc/vec.rs:1555:10
  5. stack backtrace:
  6. at /checkout/src/libstd/sys/unix/backtrace/tracing/gcc_s.rs:49
  7. 1: std::sys_common::backtrace::_print
  8. at /checkout/src/libstd/sys_common/backtrace.rs:71
  9. 2: std::panicking::default_hook::{{closure}}
  10. at /checkout/src/libstd/sys_common/backtrace.rs:60
  11. at /checkout/src/libstd/panicking.rs:381
  12. 3: std::panicking::default_hook
  13. at /checkout/src/libstd/panicking.rs:397
  14. 4: std::panicking::rust_panic_with_hook
  15. at /checkout/src/libstd/panicking.rs:611
  16. 5: std::panicking::begin_panic
  17. at /checkout/src/libstd/panicking.rs:572
  18. at /checkout/src/libstd/panicking.rs:522
  19. 7: rust_begin_unwind
  20. 8: core::panicking::panic_fmt
  21. at /checkout/src/libcore/panicking.rs:71
  22. 9: core::panicking::panic_bounds_check
  23. at /checkout/src/libcore/panicking.rs:58
  24. 10: <alloc::vec::Vec<T> as core::ops::index::Index<usize>>::index
  25. at /checkout/src/liballoc/vec.rs:1555
  26. 11: panic::main
  27. at src/main.rs:4
  28. 12: __rust_maybe_catch_panic
  29. at /checkout/src/libpanic_unwind/lib.rs:99
  30. 13: std::rt::lang_start
  31. at /checkout/src/libstd/panicking.rs:459
  32. at /checkout/src/libstd/panic.rs:361
  33. at /checkout/src/libstd/rt.rs:61
  34. 14: main
  35. 16: <unknown>

示例 9-2:当设置 RUST_BACKTRACE 环境变量时 panic! 调用所生成的 backtrace 信息

这里有大量的输出!你实际看到的输出可能因不同的操作系统和 Rust 版本而有所不同。为了获取带有这些信息的 backtrace,必须启用 debug 标识。当不使用 —release 参数运行 cargo build 或 cargo run 时 debug 标识会默认启用,这里便是如此。

本章的后面会再次回到 并讲到何时应该及何时不应该使用这个方式。接下来,我们来看看如何使用 Result 来从错误中恢复。