Rustのpanic!()の詳しい解説【初心者向け】

616, 2023-01-04

目次

Rustのpanic!()とは?

Rustのpanic!()はエラー処理で使われるものです。
Rustのエラー処理は大きく分けて2つに分類されます。

それは

  • 回復可能なエラー

  • 回復不能なエラー

の2つです。
つまりそのエラーが回復できるかどうか? でエラー処理が分かれるわけですね。

panic!()回復不能なエラーを処理するために使われるマクロ関数です。
この記事ではRustのpanic!()について解説していきます。

Rustのpanic!()の概要

Rustのpanic!()はなんのためのマクロ関数か?
というところですが、これはプログラムの実行を中止するための関数です。

panic!()が実行されるとRustのプログラムは実行を中止します。

panic!()の実行中止時のふるまいは大きく分けて2通りあります。
それは

  • スタックを巻き戻す

  • 異常終了する

の2つのうちのどちらかになります。

スタックを巻き戻すとは、Rustがスタックをさかのぼって実行してきた各関数の変数などを片付けることを言います。
つまりpanic!()したら自動で呼び出した関数のデータを片付けてくれるということですね。
なんてお行儀が良いんでしょうか。

これと対照的なのが異常終了する場合です。
異常終了する場合は関数のデータを片付けずに即座にプログラムを終了させます。
ですのでこの場合はメモリ上のデータはOSに片付けてもらう必要があります。
お行儀が悪いですね。

これらの2つの振る舞いはCargo.tomlの設定で変更可能です。
panic!()はデフォルトではスタックの巻き戻しをする設定になっています。

panic!で異常終了するようにする

panic!()で即座に異常終了させたい場合。
その場合はCargo.tomlに↓の設定を追記します。

[profile.release]
panic = 'abort'

↑のようにするとリリースビルドのプログラムでpanic!()が異常終了するようになります。

Rustのpanic!()の構造

Rustのpanic!()は↓のような構造になっています。

panic!(エラーメッセージ, 引数...);

エラーメッセージは文字列で、表示のための引数を渡すことができます。
これはほぼprintln!()などと変わりありません。

panic!()を使ってみる

じっさいにpanic!()を使ってみましょう。
↓のようなコードを書きます。

fn main() {
    panic!("クラッシュしました!");
}

cargo runを実行すると↓のような結果になります。

$ cargo run
   Compiling panic v0.1.0 (/tmp/rust/panic)
    Finished dev [unoptimized + debuginfo] target(s) in 1.83s
     Running `target/debug/panic`
thread 'main' panicked at 'クラッシュしました!', src/main.rs:2:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

↑のメッセージの見方ですが、まず「thread 'main'」と書かれています。
これはpanic!()がメインスレッドで実行されたことを表しています。

次に「クラッシュしました!」というメッセージが表示されて、そのあとに「src/main.rs:2:5」と表示されます。
これはmain.rsファイルの2行目5文字目でパニックが起こったことを表しています。
つまりどこでパニックしたかわかるようになっているんですね。

そしてnote: run with 'RUST_BACKTRACE=1' environment variable to display a backtrace'と書かれています。
これはプログラム実行時に環境変数のRUST_BACKTRACE1にするとバックトレースが表示されますよ、というメッセージです。

RUST_BACKTRACEでバックトレースを表示する

じっさいに言われた通り↓のようにして実行してみます。

$ RUST_BACKTRACE=1 cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.04s
     Running `target/debug/panic`
thread 'main' panicked at 'クラッシュしました!', src/main.rs:2:5
stack backtrace:
   0: rust_begin_unwind
             at /build/rustc-6496Ax/rustc-1.57.0+dfsg1+llvm/library/std/src/panicking.rs:517:5
   1: core::panicking::panic_fmt
             at /build/rustc-6496Ax/rustc-1.57.0+dfsg1+llvm/library/core/src/panicking.rs:100:14
   2: panic::main
             at ./src/main.rs:2:5
   3: core::ops::function::FnOnce::call_once
             at /build/rustc-6496Ax/rustc-1.57.0+dfsg1+llvm/library/core/src/ops/function.rs:227:5
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.

そうすると↑のようにRustのバックトレースが表示されました。
エラー調査をしたい場合はバックトレースを見ながらエラーの場所を調査するということがよく行われます。
環境変数のRUST_BACKTRACE1にすればこのようにバックトレースが見れます。

Windowsのコマンドプロンプトを使う場合は?

さきほどの環境変数の指定はLinux系の端末での指定でした。
Windowsのコマンドプロンプトを使う場合は↓のようにすると環境変数をセットできます。

> set RUST_BACKTRACE=1
> cargo run

環境変数をオフにする場合は

> set RUST_BACKTRACE=0

で値を0にしておきます。

フルバックトレースを表示する

RUST_BACKTRACEfullにしておくとフルバックトレースが表示されます。
実行するとかなり長いトレースが出力されます。
お試しください。

ベクタの添え字でpanic!()させる

明示的にpanic!()を呼び出さなくても回復不能なエラーが発生した場合はRustはパニックします。
たとえば↓のコードを見てください。

fn vector() {
    let v = vec![1, 2, 3];

    v[100];
}

このコードを実行すると↓のような結果になります。

$ cargo run
   Compiling panic v0.1.0 (/tmp/rust/panic)
    Finished dev [unoptimized + debuginfo] target(s) in 2.40s
     Running `target/debug/panic`
thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 100', src/main.rs:8:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

'index out of bounds: the len is 3 but the index is 100'」というパニックが発生しています。
これは「ベクタの長さは3だけどインデックスが100になっているよ!」というエラーメッセージです。
バックトレースを見てみると

$ RUST_BACKTRACE=1 cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.04s
     Running `target/debug/panic`
thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 100', src/main.rs:8:5
stack backtrace:
   0: rust_begin_unwind
             at /build/rustc-6496Ax/rustc-1.57.0+dfsg1+llvm/library/std/src/panicking.rs:517:5
   1: core::panicking::panic_fmt
             at /build/rustc-6496Ax/rustc-1.57.0+dfsg1+llvm/library/core/src/panicking.rs:100:14
   2: core::panicking::panic_bounds_check
             at /build/rustc-6496Ax/rustc-1.57.0+dfsg1+llvm/library/core/src/panicking.rs:76:5
   3: <usize as core::slice::index::SliceIndex<[T]>>::index
             at /build/rustc-6496Ax/rustc-1.57.0+dfsg1+llvm/library/core/src/slice/index.rs:184:10
   4: core::slice::index::<impl core::ops::index::Index<I> for [T]>::index
             at /build/rustc-6496Ax/rustc-1.57.0+dfsg1+llvm/library/core/src/slice/index.rs:15:9
   5: <alloc::vec::Vec<T,A> as core::ops::index::Index<I>>::index
             at /build/rustc-6496Ax/rustc-1.57.0+dfsg1+llvm/library/alloc/src/vec/mod.rs:2496:9
   6: panic::vector
             at ./src/main.rs:8:5
   7: panic::main
             at ./src/main.rs:2:5
   8: core::ops::function::FnOnce::call_once
             at /build/rustc-6496Ax/rustc-1.57.0+dfsg1+llvm/library/core/src/ops/function.rs:227:5
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.

「5:」のところにベクタのインデックスのトレースが出ています。
このようにバックトレースを見ればどこからパニックが伝搬しているかわかります。

おわりに

今回はRustのpanic!()について詳しく解説しました。
Rustのpanic!()はエラー処理ではよく使われますので押さえておきましょう。
なにか参考になれば幸いです。

(^ _ ^)

パニック!

(・ v ・)

混乱する!



この記事のアンケートを送信する