Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Panic

Rust 提供了一种机制来阻止函数正常返回,而是“panic“,这是对通常不期望在遇到错误的上下文中可恢复的错误条件的响应。

某些语言构造(如越界数组索引)会自动 panic。

还有一些语言功能提供对 panic 行为的控制级别:

Note

标准库提供了通过 panic!显式 panic 的能力。

panic_handler 属性

panic_handler 属性可应用于函数以定义 panic 的行为。

panic_handler 属性只能应用于签名 fn(&PanicInfo) -> ! 的函数。

Note

PanicInfo 结构体包含有关 panic 位置的信息。

依赖图中必须有一个单一的 panic_handler 函数。

下面显示了一个记录 panic 消息然后停止线程的 panic_handler 函数。

#![no_std]

use core::fmt::{self, Write};
use core::panic::PanicInfo;

struct Sink {
    // ..
   _0: (),
}

impl Sink {
    fn new() -> Sink { Sink { _0: () }}
}

impl fmt::Write for Sink {
    fn write_str(&mut self, _: &str) -> fmt::Result { Ok(()) }
}

#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
    let mut sink = Sink::new();

    // logs "panicked at '$reason', src/main.rs:27:4" to some `sink`
    let _ = writeln!(sink, "{}", info);

    loop {}
}

标准行为

std 提供两种不同的 panic 处理器:

  • unwind — 展开栈并可能可恢复。
  • abort –– 中止进程且不可恢复。

并非所有目标都可能提供 unwind 处理器。

Note

链接 std 时使用的 panic 处理器可以通过 -C panic CLI 标志设置。大多数目标的默认值是 unwind

标准库的 panic 行为可以在运行时通过 std::panic::set_hook 函数修改。

链接 no_std 二进制文件、dylib、cdylib 或 staticlib 将需要指定您自己的 panic 处理器。

Panic 策略

_Panic 策略_定义了 crate 构建时支持的 panic 行为类型。

Note

可以在 rustc 中使用 -C panic CLI 标志选择 panic 策略。

当生成二进制文件、dylib、cdylib 或 staticlib 并链接 std 时,-C panic CLI 标志还影响使用哪个panic 处理器

Note

使用 abort panic 策略编译代码时,优化器可以假设跨 Rust 帧展开是不可能的,这可能导致代码大小和运行时速度的改进。

Note

有关链接具有不同 panic 策略的 crate 的限制,请参阅 link.unwinding。这意味着使用 unwind 策略构建的 crate 可以使用 abort panic 处理器,但 abort 策略不能使用 unwind panic 处理器。

展开

Panic 可能是可恢复的或不可恢复的,尽管可以配置(通过选择非展开 panic 处理器)使其始终不可恢复。(反过来不成立:unwind 处理器不保证所有 panic 都是可恢复的,只保证通过 panic! 宏和类似标准库机制的 panic 是可恢复的。)

当 panic 发生时,unwind 处理器“展开“ Rust 帧,就像 C++ 的 throw 展开 C++ 帧一样,直到 panic 到达恢复点(例如在线程边界处)。这意味着当 panic 遍历 Rust 帧时,这些帧中实现 Drop 的活动对象将调用其 drop 方法。因此,当恢复正常执行时,不再可访问的对象将被“清理“,就像它们正常超出作用域一样。

Note

只要此资源清理的保证得到保留,“展开“可以在不实际使用 C++ 用于目标平台的机制的情况下实现。

Note

标准库提供了两种从 panic 恢复的机制,std::panic::catch_unwind(允许在 panic 线程内恢复)和 std::thread::spawn(自动为生成的线程设置 panic 恢复,以便其他线程可以继续运行)。

跨 FFI 边界的展开

可以使用适当的 ABI 声明跨 FFI 边界展开。虽然在某些情况下有用,但这为未定义行为创造了独特的机会,特别是当涉及多个语言运行时时。

使用错误的 ABI 展开是未定义行为:

  • 从通过使用非展开 ABI(如 "C""system" 等)声明的函数声明或指针调用的外部函数导致展开进入 Rust 代码。(例如,当 C++ 中编写的此类函数抛出未捕获的异常并传播到 Rust 时,会发生这种情况。)
  • 从不支持展开的代码(如使用 -fno-exceptions 的 GCC 或 Clang 编译的代码)调用展开的 Rust extern 函数(使用 extern "C-unwind" 或允许展开的其他 ABI)

使用 std::panic::catch_unwindstd::thread::JoinHandle::join 或让其传播到 Rust main() 函数或线程根之外来捕获外部展开操作(如 C++ 异常)将具有以下两种行为之一,并且未指定会发生哪种:

  • 进程中止。
  • 函数返回包含不透明类型的 Result::Err

Note

使用不同 Rust 标准库实例编译或链接的 Rust 代码在此保证中被视为“外部异常“。因此,使用 panic! 并链接到一个版本的 Rust 标准库的库,从使用不同版本标准库的应用程序调用,即使该库仅在子线程内使用,也可能导致整个应用程序中止。

目前没有关于外部运行时尝试处置或重新抛出 Rust panic 负载时发生的行为的保证。换句话说,源自 Rust 运行时的展开必须导致进程终止或被同一运行时捕获。