catch_unwind 能让 panic 不拖垮整个程序,但它不是 try/catch。这篇验证它能抓什么、载荷怎么还原、为什么要 UnwindSafe、毒化 Mutex 是怎么回事,以及哪些情况下它完全没用。
编译:成功
运行(退出码 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。
源码:
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 编译:成功
运行(退出码 0):
is_poisoned = true
lock 返回 Err(PoisonError),但数据还在: [1, 2, 3, 4]
clear_poison 之后 is_poisoned = falseMutex 的基础见Mutex<T> 与内部可变性。
编译:成功
运行(退出码 0):
hook 被调用: 原始 panic
第一次捕获后继续
resume_unwind 之后又被捕获了:is_err = true;hook 没有再被调用(只打印过一次 hook)
panic = "abort":被信号 6 终止(返回码 -6),stdout=''(catch_unwind 没有机会返回) 源码:
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.