take4s5i DEV

7 Dec 2021

Rust panic を理解する

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を使って変更できるようです。

Profiles - The Cargo Book

  • panicunwindにするとunwindingを行う
  • panicabortにするとプロセスをその場で以上終了する。

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を捕捉できます。

参考