3-2. panic

Rustのpanicは、プログラムの異常終了処理を安全に行うための機構です。例えば、下記のようなスライスの境界外アクセスは、panicを発生させます。

fn main() {
    let s: &[u8] = &[1, 2, 3, 4];
    println!("{}", s[100]);
}

上のプログラムを実行すると、下記のようなpanic発生のエラーが出力されます。

thread 'main' panicked at 'index out of bounds: the len is 4 but the index is 100', src/main.rs:3:20

C言語の未定義動作と異なり、Rustでは定義されたpanicハンドラでプログラミングエラーに対処します。OSにホストされている環境では、panicハンドラの処理が完了すると、プロセスを強制終了します。このプロセスの強制終了も、定義された動作です。

Rustのpanicについては、簡潔なQ Rustのパニック機構が詳しいです。こちらの解説にある通り、panicの主な処理は、stdクレート (std::panicに公開API、std::panicking.rsにpanic処理の本体) にあります。そのため、stdクレートをリンクしない#![no_std]なプログラムでは、panicハンドラが未定義のままになっています。

そこで、#[panic_handler]アトリビュートを使って、panicハンドラを定義します。最小限の#![no_std]プログラムは、次のようになります。

#![no_main]
#![no_std]

use core::panic::PanicInfo;

#[panic_handler]
fn panic(_panic: &PanicInfo<'_>) -> ! {
    loop {}
}

このPanicInfoは、panicに関する情報を提供します。Rust 1.26からは、Displayトレイトが実装されているため、フォーマットが使える環境を作ることで、panic発生時の情報を容易に得ることができます。まず、stdクレートを使い、簡単に実験できるサンプルコードをお見せします。

#![allow(unused)]
fn main() {
    use std::panic;

    panic::set_hook(Box::new(|panic_info| {
        println!("{}", panic_info);
    }));

    panic!("Normal panic");
}

実行結果は、次のようになります。

panicked at 'Normal panic', src/main.rs:9:1

このことは、no_std環境でも同じように使うことができます。no_std環境でのprint!マクロ実装方法は、print!マクロで紹介します。

use core::panic::PanicInfo;
#[panic_handler]
pub fn panic(info: &PanicInfo) -> ! {
    println!("{}", info);
    loop {}
}

assert!マクロの失敗でも同様の情報が得られるため、非常に有用なテクニックです。

no_std環境で利用可能なpanicハンドラを提供するクレートも存在しています。

  • panic-abortは、パニックが発生すると、アボート命令を実行します。
  • panic-haltは、パニックが発生すると、無限ループに入ります。
  • panic-itmは、ARM Cortex-Mがターゲットの時に利用できるクレートで、パニック発生時のメッセージをITM経由でログを出力します。
  • panic-semihostingは、ARM Cortex-Mがターゲットの時に利用できるクレートで、パニック発生時のメッセージを、セミホスティング機能を使ってログ出力します。

panic-abortの実装を見ると、30行しかありません。わざわざクレートにする理由はあるのでしょうか?

panicハンドラをクレートに切り分けることで、コンパイル時のプロファイルでpanicハンドラを切り替える場合に、便利です。

// 開発プロファイル:パニックをデバッグしやすくします。`rust_begin_unwind`にブレイクポイントが置けます。
#[cfg(debug_assertions)]
extern crate panic_halt;

// リリースプロファイル:バイナリサイズを最小化します。
#[cfg(not(debug_assertions))]
extern crate panic_abort;

上記コードでは、cargo buildした時はpanic-haltクレートと、cargo build --releaseした時はpanic-abortクレートとリンクします。

出典