发生 panic
发生 panic 是 Rust 语言的核心部分。像索引这样的内置操作会在运行时进行内存安全检查。当尝试进行越界索引时,会导致 panic。
在标准库中,发生 panic 有明确的行为:它会展开发生 panic 的线程的堆栈,除非用户选择在发生 panic 时中止程序。
然而,在没有标准库的程序中,panic 的行为是未定义的。可以通过声明一个 #[panic_handler]
函数来选择行为。这个函数必须在程序的依赖图中出现一次,并且必须具有以下签名:fn(&PanicInfo) -> !
,其中 PanicInfo
是一个包含 panic 位置信息的结构体。
鉴于嵌入式系统从面向用户到安全关键(不能崩溃)不等,因此没有一种通用的 panic 行为,但有很多常用的行为。这些常用行为已被打包成 crate,这些 crate 定义了 #[panic_handler]
函数。一些示例包括
panic-abort
。发生 panic 会导致执行中止指令。panic-halt
。发生 panic 会导致程序或当前线程通过进入无限循环而停止。panic-itm
。使用 ITM(一种特定于 ARM Cortex-M 的外设)记录 panic 消息。panic-semihosting
。使用半主机技术将 panic 消息记录到主机。
您或许可以在 crates.io 上搜索 panic-handler
关键字找到更多 crate。
程序可以通过链接到相应的 crate 来选择这些行为之一。panic 行为在应用程序的源代码中以单行代码的形式表示,这不仅作为文档很有用,还可以用于根据编译配置文件更改 panic 行为。例如
#![no_main]
#![no_std]
// dev profile: easier to debug panics; can put a breakpoint on `rust_begin_unwind`
#[cfg(debug_assertions)]
use panic_halt as _;
// release profile: minimize the binary size of the application
#[cfg(not(debug_assertions))]
use panic_abort as _;
// ..
在此示例中,使用 dev 配置文件构建时(cargo build
)链接到 panic-halt
crate,但使用 release 配置文件构建时(cargo build --release
)链接到 panic-abort
crate。
use panic_abort as _;
形式的use
语句用于确保panic_abort
panic 处理程序包含在我们的最终可执行文件中,同时向编译器明确表示我们不会显式使用该 crate 中的任何内容。如果没有as _
重命名,编译器会警告我们存在未使用的导入。有时您可能会看到extern crate panic_abort
,这是一种在 Rust 2018 版本之前使用的旧样式,现在应该只用于 “sysroot” crate(那些与 Rust 本身一起分发的 crate),例如proc_macro
、alloc
、std
和test
。
一个例子
这是一个尝试对数组进行越界索引的示例。该操作会导致 panic。
#![no_main]
#![no_std]
use panic_semihosting as _;
use cortex_m_rt::entry;
#[entry]
fn main() -> ! {
let xs = [0, 1, 2];
let i = xs.len();
let _y = xs[i]; // out of bounds access
loop {}
}
此示例选择了 panic-semihosting
行为,该行为使用半主机将 panic 消息打印到主机控制台。
$ cargo run
Running `qemu-system-arm -cpu cortex-m3 -machine lm3s6965evb (..)
panicked at 'index out of bounds: the len is 3 but the index is 4', src/main.rs:12:13
您可以尝试将行为更改为 panic-halt
并确认在这种情况下没有打印任何消息。