9.1 不可恢复的错误以及panic!
9.1.1 Rust错误处理概述
Rust拥有极高的可靠性,而这种可靠性也延伸到了错误处理。在大部分情况下,Rust会迫使你意识到可能会出现错误的地方,然后在编译阶段就确保它们获得妥善的处理。
在Rust里,错误被分为两大类:
- 可恢复的错误:比如说文件未找到。这种情况下,可以把错误信息传递给用户,并让用户再次尝试。
- 不可恢复的错误:bug的另外一种说法,比如说索引越界。
其他大部分编程语言都没有刻意地区分这两种错误。它们通常通过异常这种单一机制来统一处理。但Rust里没有类似异常的机制。
- 针对可恢复的错误,Rust提供了
Result<T, E>这个类型,9.2. Result枚举与可恢复的错误 Pt.1会讲。 - 针对不可恢复的错误,Rust提供了
panic!这个宏。当执行这个宏时,程序会中止执行。
9.1.2 panic!
有时候在代码里会发生糟糕的问题,而针对这些问题开发者几乎束手无策。为了应对这种情况,Rust提供了panic!这个宏。
当这个宏执行时,会发生这些动作:
- 它会打印出错误信息。
- 然后展开(unwind)和清理调用栈。
- 退出程序。
9.1.3 当panic!时:展开还是终止
展开调用栈时工作量极大,因为Rust会沿着调用栈往回走,清理沿途遇到的每个函数中的数据。
与之相反,Rust也提供了终止(abort)的选择。这种做法就是不进行清理,直接停止程序,程序所使用的内存稍后交给操作系统来清理。
如果你想要二进制文件更小,就把设置从“展开”改为“终止”:在Cargo.toml中适当的profile部分设置panic = "abort"。
以我的Cargo.toml为例:
[package]
name = "RustStudy"
version = "0.1.0"
edition = "2021"
[dependencies]
rand = "0.8.5"
[profile.release]
panic = "abort"
profile.release的意思是在发布模式下运行时。
9.1.4 panic!宏
看一个panic!宏的例子:
fn main() {
panic!("Something went wrong");
}
非常简单的例子。panic!宏的参数就是错误信息,它会在程序停止时被打印出来。
输出:
thread 'main' panicked at src/main.rs:2:5:
Something went wrong
stack backtrace:
0: __rustc::rust_begin_unwind
at /rustc/ac68faa20c58cbccd01ee7208bf3b6e93a7d7f96/library/std/src/panicking.rs:689:5
1: core::panicking::panic_fmt
at /rustc/ac68faa20c58cbccd01ee7208bf3b6e93a7d7f96/library/core/src/panicking.rs:80:14
2: RustStudy::main
at ./src/main.rs:2:5
3: core::ops::function::FnOnce::call_once
at /Users/stanyin/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/src/rust/library/core/src/ops/function.rs:250:5
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
在前面几篇文章中也有程序恐慌的时候,但是那时候我没有把stack backtrace粘贴进文章,因为当时还没有讲到这里。上面呈现的才是完整的恐慌信息。现在我们来解析一下:
- 第一行告诉了程序恐慌的位置——在
src目录下的main.rs里的第2行第5列。 - 第二行是程序定义的错误信息。
- 从第三行开始的
stack backtrace指的是回溯信息。在标记为2的位置就是main.rs。回溯信息里包含了到达发生错误的地点的所有被调用函数的列表,而在它下边——也就是标记为3的地方——就是调用了我们代码的代码,可能包含了Rust的核心库、标准库,抑或是第三方的库。 - 最后一行的
note说可以把RUST_BACKTRACE设为full来获得所有的详细信息。在Windows上,在终端输入set RUST_BACKTRACE=full && cargo run。在macOS/Linux上,输入export RUST_BACKTRACE=full && cargo run。
为了获得像刚才这样的调试信息,还有一点前提条件:程序必须运行在调试模式(debug)下,而不是发布模式(--release)。cargo build和cargo run都是默认调试模式,所以只要确保不带--release这个flag就行。