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 panicCLI 标志设置。大多数目标的默认值是unwind。标准库的 panic 行为可以在运行时通过
std::panic::set_hook函数修改。
链接 no_std 二进制文件、dylib、cdylib 或 staticlib 将需要指定您自己的 panic 处理器。
Panic 策略
_Panic 策略_定义了 crate 构建时支持的 panic 行为类型。
Note
可以在
rustc中使用-C panicCLI 标志选择 panic 策略。当生成二进制文件、dylib、cdylib 或 staticlib 并链接
std时,-C panicCLI 标志还影响使用哪个panic 处理器。
Note
使用
abortpanic 策略编译代码时,优化器可以假设跨 Rust 帧展开是不可能的,这可能导致代码大小和运行时速度的改进。
Note
有关链接具有不同 panic 策略的 crate 的限制,请参阅 link.unwinding。这意味着使用
unwind策略构建的 crate 可以使用abortpanic 处理器,但abort策略不能使用unwindpanic 处理器。
展开
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 编译的代码)调用展开的 Rustextern函数(使用extern "C-unwind"或允许展开的其他 ABI)
使用 std::panic::catch_unwind、std::thread::JoinHandle::join 或让其传播到 Rust main() 函数或线程根之外来捕获外部展开操作(如 C++ 异常)将具有以下两种行为之一,并且未指定会发生哪种:
- 进程中止。
- 函数返回包含不透明类型的
Result::Err。
Note
使用不同 Rust 标准库实例编译或链接的 Rust 代码在此保证中被视为“外部异常“。因此,使用
panic!并链接到一个版本的 Rust 标准库的库,从使用不同版本标准库的应用程序调用,即使该库仅在子线程内使用,也可能导致整个应用程序中止。
目前没有关于外部运行时尝试处置或重新抛出 Rust panic 负载时发生的行为的保证。换句话说,源自 Rust 运行时的展开必须导致进程终止或被同一运行时捕获。