panicしたら異常終了するぐらいにしか理解していなかったので。 いろいろ調べたり実験したりしてみました。
panic したときの挙動
#[derive(Debug)]
struct Data(u32);
impl Drop for Data {
fn drop(&mut self){
println!("drop {:?}", self);
}
}
fn call_recurse(n: u32) {
if n == 0 {
panic!("panic!");
}
let data = Data(n);
call_recurse(n - 1);
println!("return: {:?}", data);
}
fn main() {
call_recurse(4);
}
出力:
Compiling playground v0.0.1 (/playground)
Finished dev [unoptimized + debuginfo] target(s) in 1.07s
Running `target/debug/playground`
thread 'main' panicked at 'panic!', src/main.rs:12:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
drop Data(1)
drop Data(2)
drop Data(3)
drop Data(4)
Drop トレイトを実装した構造体の生存中にpanicしたところ、ちゃんとdropされました。
この挙動はunwindingというようです。
unwind と abort
panic時の挙動ですが、Cargoを使って変更できるようです。
panicをunwindにするとunwindingを行うpanicをabortにするとプロセスをその場で以上終了する。
unwindするとスレッドがパニックしてもハンドリングできるためプログラムは頑強になりそうです。
が、unwinding用のコードが各関数に埋め込まれるためコードサイズも大きくなるようなので一長一短ですね。
デーモンやWebサーバのといった長生きするプログラム以外では abort にしてしまってもいいかもしれません。
panicのハンドリング
set_hook
use std::panic;
use std::thread;
fn main() {
std::panic::set_hook(Box::new(|_| {
println!("Custom panic hook");
}));
let handle = thread::spawn(|| {
panic!();
});
match handle.join() {
Err(_) => println!("thread paniced"),
Ok(_) => println!("thread exit successfly"),
}
panic!();
}
出力:
Custom panic hook
thread paniced
Custom panic hook
set_hook を使ってpanic時の処理をクロージャで渡します。
panicが起きると何度も呼ばれるようです。
このコードだとspawnした子スレッドのpanicとメインスレッドのpanicで2回呼ばれています。
set_hook で登録したパニックハンドラはtake_hookという関数で登録解除できます。
Thread
さっきしれっとやってましたがThreadもpanicをハンドリングする方法の1つです。
子スレッドでパニックすると親スレッド側でjoinしたときにErrが返ります。
catch_unwind
use std::panic;
fn main() {
let result = panic::catch_unwind(|| {
println!("hi!");
panic!();
println!("bye!");
});
match result {
Err(_) => println!("paniced"),
Ok(_) => println!("successfly"),
}
}
出力:
hi!
paniced
catch_unwindを使うとスレッドを使わずにpanicを捕捉できます。