恐慌

恐慌是 Rust 语言的核心部分。内置操作(如索引)在运行时检查内存安全。当尝试越界索引时,会导致恐慌。

在标准库中,恐慌具有定义的行为:它会展开恐慌线程的堆栈,除非用户选择在恐慌时中止程序。

然而,在没有标准库的程序中,恐慌行为是未定义的。可以通过声明一个 #[panic_handler] 函数来选择行为。此函数必须在程序的依赖关系图中恰好出现 一次,并且必须具有以下签名:fn(&PanicInfo) -> !,其中 PanicInfo 是一个包含有关恐慌位置信息的结构体。

鉴于嵌入式系统从面向用户到安全关键(不能崩溃)不等,没有一种通用的恐慌行为,但有很多常用的行为。这些常见行为已被打包到定义 #[panic_handler] 函数的板条箱中。一些例子包括

  • panic-abort。恐慌会导致执行中止指令。
  • panic-halt。恐慌会导致程序或当前线程通过进入无限循环而停止。
  • panic-itm。恐慌消息使用 ITM(ARM Cortex-M 特定的外设)进行记录。
  • panic-semihosting。恐慌消息使用半主机技术记录到主机。

您可能能够在 crates.io 上搜索 panic-handler 关键字找到更多板条箱。

程序可以通过简单地链接到相应的板条箱来选择其中一种行为。恐慌行为在应用程序源代码中以单行代码的形式表达,这不仅对文档很有用,而且还可以用于根据编译配置文件更改恐慌行为。例如

#![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 _;

// ..

在此示例中,当使用开发配置文件(cargo build)构建时,板条箱链接到 panic-halt 板条箱,但当使用发布配置文件(cargo build --release)构建时,链接到 panic-abort 板条箱。

use panic_abort as _; 形式的 use 语句用于确保 panic_abort 恐慌处理程序包含在我们的最终可执行文件中,同时向编译器明确表示我们不会显式使用板条箱中的任何内容。如果没有 as _ 重命名,编译器会警告我们有一个未使用的导入。有时您可能会看到 extern crate panic_abort,这是一种旧的风格,在 Rust 的 2018 版之前使用,现在应该只用于“系统根”板条箱(与 Rust 本身一起分发的板条箱),例如 proc_macroallocstdtest

一个例子

以下是一个尝试索引数组超出其长度的示例。该操作会导致恐慌。

#![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 行为,该行为使用半主机将恐慌消息打印到主机控制台。

$ 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,并确认在这种情况下不会打印任何消息。