bigywater.tech
Rust · 76

catch_unwind

catch_unwind 能让 panic 不拖垮整个程序,但它不是 try/catch。这篇验证它能抓什么、载荷怎么还原、为什么要 UnwindSafe、毒化 Mutex 是怎么回事,以及哪些情况下它完全没用。

3 种
载荷类型:&str(字面量)、String(带格式)、panic_any 的任意类型
E0277
闭包捕获 &mut 时不是 UnwindSafe
SIGABRT
panic = "abort" 或展开中再 panic 时进程的终止信号

载荷与捕获

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    没有 panic: Ok(2)
    panic!("字面量") 的载荷: &str = Some("固定字符串")  String = None
    panic!("带 {}", n) 的载荷: &str = None  String = Some("带参数 7")
    panic_any(42i32) 的载荷: i32 = Some(42)
    程序继续运行

panic 本身的行为见panic!、unwrap 与 expect。

UnwindSafe

实测 · 本机执行输出
  源码:
      1 | use std::panic;
      2 | fn main() {
      3 |     let mut count = 0;
      4 |     let r = panic::catch_unwind(|| { count += 1; panic!("x"); });
      5 |     println!("{:?} {}", r.is_err(), count);
      6 | }
  编译:失败
    error[E0277]: the type `&mut i32` may not be safely transferred across an unwind boundary
       --> <实验目录>/src/u76b.rs:4:33
        |
      4 |     let r = panic::catch_unwind(|| { count += 1; panic!("x"); });
        |             ------------------- --^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        |             |                   |
        |             |                   `&mut i32` may not be safely transferred across an unwind boundary
        |             |                   within this `{closure@u76b.rs:4:33}`
        |             required by a bound introduced by this call
        |
        = help: within `{closure@u76b.rs:4:33}`, the trait `UnwindSafe` is not implemented for `&mut i32`
        = note: `UnwindSafe` is implemented for `&i32`, but not for `&mut i32`
    note: required because it's used within this closure
       --> <实验目录>/src/u76b.rs:4:33
        |
      4 |     let r = panic::catch_unwind(|| { count += 1; panic!("x"); });
        |                                 ^^
    note: required by a bound in `std::panic::catch_unwind`
       --> <rust 标准库源码>/library/std/src/panic.rs:358:40
        |
    358 | pub fn catch_unwind<F: FnOnce() -> R + UnwindSafe, R>(f: F) -> Result<R> {
        |                                        ^^^^^^^^^^ required by this bound in `catch_unwind`
        = note: the full name for the type has been written to '<实验目录>/bin/u76b.long-type-9898676550653636347.txt'
        = note: consider using `--verbose` to print the full type name to the console
    
    error: aborting due to 1 previous error
    
    For more information about this error, try `rustc --explain E0277`.
  编译:成功
  运行(退出码 0):
    is_err = true  闭包 panic 之前对 count 的修改仍然可见: count = 1

Mutex 毒化

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    is_poisoned = true
    lock 返回 Err(PoisonError),但数据还在: [1, 2, 3, 4]
    clear_poison 之后 is_poisoned = false

Mutex 的基础见Mutex<T> 与内部可变性。

resume_unwind 与 abort

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    hook 被调用: 原始 panic
    第一次捕获后继续
    resume_unwind 之后又被捕获了:is_err = true;hook 没有再被调用(只打印过一次 hook)
  panic = "abort":被信号 6 终止(返回码 -6),stdout=''(catch_unwind 没有机会返回)

Drop 里再 panic

实测 · 本机执行输出
  源码:
      1 | struct Bomb; impl Drop for Bomb { fn drop(&mut self) { println!("Bomb::drop 里 panic"); panic!("drop 里的 panic"); } }
      2 | fn main() { let _b = Bomb; println!("主函数先 panic"); panic!("第一个 panic"); }
  编译:成功
  运行(返回码 -6,即被信号 6 终止):
    stdout: 主函数先 panic | Bomb::drop 里 panic
    stderr(去掉回溯栈帧):
      thread 'main' (7134885) panicked at <实验目录>/src/u76g.rs:2:56:
      第一个 panic
      note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
      thread 'main' (7134885) panicked at <实验目录>/src/u76g.rs:1:89:
      drop 里的 panic
      thread 'main' (7134885) panicked at /rustc/48a229ceaefd4985c50990b14116b6d856af0985/library/core/src/panicking.rs:233:5:
      panic in a destructor during cleanup
      thread caused non-unwinding panic. aborting.

分步演示

catch_unwind
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / rustc 1.98.1(brew)/ cargo 1.98.1;单个文件用 rustc 直接编译,需要依赖时用 cargo(经代理从 crates.io 下载);计时用 std::time::Instant,数字每次运行会有小幅抖动。panic = "abort" 通过 cargo profile.dev 设置
    • 没有验证:跨 FFI 边界的 panic(extern "C" 里的展开行为)、catch_unwind 的性能开销、其它非 Rust 异常、线程里 catch_unwind 与 join 的组合
    京ICP备2026067502号-1京公网安备11010502063487号