0%

Ch 09.01:使用`panic!`的backtrace

使用panic!的backtrace

让我们来看看另外一个例子,当因为我们自己的代码有bug而导致库函数调用panic!是什么样的。如Listing 9-1所示的代码尝试访问超出范围的vector的索引。

Filename: src/main.rs

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

v[99];
}

Listing 9-1: Attempting to access an element beyond the end of a vector, which will cause a call to panic!

这里,我们尝试去访问第100个元素(索引99就是第100个元素,索引是从0开始的),但是vector只有3个元素,肯定会报错。在这个场景中,Rust就会panic。使用[]就是企图返回一个元素,但是你传入一个无效的索引,又不会有这个元素,Rust就肯定不会返回正确的结果。

在C语言中,尝试读取数据结构之后的值是未定义行为(undefined behavior)。你可能会得到一个这个位置的对应的值,但是这个可能是任何的值,但就不是你想要访问的值,因为这个位置的值不属于你想要访问的数据结构范围的值。这个被叫做**缓冲区溢出(buffer overread)**,并且可能会导致安全漏洞,比如攻击者可以像这样操作索引来读取存储在数据结构之后不被允许访问的数据。

为了防止这种漏洞,如果尝试读取一个索引不存在的元素,Rust会停止执行并拒绝继续。尝试运行上面的程序会出现如下的错误:

1
2
3
4
5
6
$ cargo run
Compiling panic v0.1.0 (file:///projects/panic)
Finished dev [unoptimized + debuginfo] target(s) in 0.27s
Running `target/debug/panic`
thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 99', src/main.rs:4:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

上面的错误信息指出错误发生在src/main.rs文件的第4行第5个字符,我们尝试访问索引为99的元素。接着一行的信息告诉我们可以设置RUST_BACKTRACE环境变量来获取backtrace查看发生了什么错误。一个backtrace是一个已经被调用直到当前这一个点的所有的函数列表。Backtrace在Rust的工作原理和其他语言一样:阅读backtrace的关键是从头开始读一直到发现你编写的代码;往下则是你调用的代码。这些行可能包含核心Rust代码,标准库代码或用到的crate代码。让我们设置RUST_BACKTRACE环境变量在Listing 9-2中看看都输出了些什么:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ RUST_BACKTRACE=1 cargo run
thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 99', src/main.rs:4:5
stack backtrace:
0: rust_begin_unwind
at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/std/src/panicking.rs:584:5
1: core::panicking::panic_fmt
at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/core/src/panicking.rs:142:14
2: core::panicking::panic_bounds_check
at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/core/src/panicking.rs:84:5
3: <usize as core::slice::index::SliceIndex<[T]>>::index
at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/core/src/slice/index.rs:242:10
4: core::slice::index::<impl core::ops::index::Index<I> for [T]>::index
at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/core/src/slice/index.rs:18:9
5: <alloc::vec::Vec<T,A> as core::ops::index::Index<I>>::index
at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/alloc/src/vec/mod.rs:2591:9
6: panic::main
at ./src/main.rs:4:5
7: core::ops::function::FnOnce::call_once
at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/core/src/ops/function.rs:248:5
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.

Listing 9-2: The backtrace generated by a panic!displayed when the environment variable RUST_BACKTRACEis set

真是输出了一大坨啊!操作系统不同,Rust版本不同,你看到的输出可能会有所不同。为了使用这些信息进行回溯,必须启用debug模式。默认就是debug模式,只要在cargo build或者cargo run之后不要加--release参数。

在Listing 9-2的第6行的backtrace指出了我们项目造成的问题:src/main.rs的第4行。如果我们不想我们的程序panic,我们就应该开始检查定位到第一行我们自己写的代码。在Listing 9-1中,我们故意写了造成panic的代码,修复这个panic就是不要去访问超出vector范围的索引。当你的代码在将来panic, 你需要弄清楚代码使用什么数据做了什么样的操作,以及正确情况下代码应该做什么。

本章后面的小节 “To panic! Or not to panic! 部分讲回panic!,详细讲解应该或者不应该用panic!来处理错误情况。接下来一节我们来讲解如何使用Result恢复一个错误。

宇宙山河浪漫,赞赏动力无限

Welcome to my other publishing channels