发生 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_macroallocstdtest

一个例子

这是一个尝试对数组进行越界索引的示例。该操作会导致 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 并确认在这种情况下没有打印任何消息。