// dictionary
パニック.
パニック(panic)は、回復不能なエラーが起きたときに現在のスレッドの実行を中断する仕組みです1。中断の際はメッセージと発生位置を標準エラー出力へ表示し、既定の設定ではスタックを巻き戻しながら値を片付けます。main関数を実行するスレッドでパニックすると、プログラム全体が終了コード101で終わります1。
パニックはpanic!マクロで明示的に起こせるほか、境界外アクセスのように言語や標準ライブラリの側から暗黙に起こることもあります2。失敗を呼び出し元へ返して処理を委ねるResult型と違い、パニックは「この先を続けても意味がない」と判断したときの打ち切り手段です。
fn withdraw(balance: u32, amount: u32) -> u32 {
if amount > balance {
panic!("残高不足です(残高{balance}円、引き出し{amount}円)");
}
balance - amount
}
fn main() {
println!("残り{}円", withdraw(3000, 1000));
println!("残り{}円", withdraw(3000, 5000)); // ここで中断する
println!("この行は実行されません");
}Playgroundで開く暗黙にパニックする場面
panic!を書いていなくても、次の操作は条件を満たした瞬間にパニックします。いずれもコンパイルは通るため、実行して初めて発覚します。
| 操作 | パニックする条件 |
|---|---|
添字アクセスv[i]・&v[a..b] |
添字や範囲が長さを超える(配列型・スライス・ベクタ(Vec)・文字列スライス) |
整数の除算/・剰余% |
右辺が0、または符号あり整数型のMIN / -13 |
| 整数の加減乗算・符号反転・シフト | 検査が有効なビルドでのオーバーフロー(シフト量がビット幅以上の場合を含む)3 |
Option型・Result型のunwrap・expect |
中身がNone・Errのとき |
オーバーフローだけは検査の有無で結果が変わるため、パニックするかどうかがビルドプロファイルに依存します。整数演算そのものの規則は数値演算にまとめてあります。
巻き戻し(unwind)と中止(abort)
パニック後の後片付けには2つの戦略があり、ビルド時に選びます2。
unwindのDrop呼び出しは通常のスコープの終了と同じで、スレッド境界などの回復点まで戻れます。abortはメモリの解放をOSに任せるぶんバイナリが小さくなり、最適化も効きやすくなります2。
補足
オーバーフローの検査はビルドプロファイル依存
オーバーフローの検査は、既定ではdevプロファイル(およびその設定を継承するtestプロファイル)でのみ有効です5。同じコードでもreleaseビルドではパニックせず、2の補数で範囲の反対側へ折り返した値になります3。
fn main() {
let stock: u8 = 250;
let arrival: u8 = "10".parse().unwrap(); // 実行時に決まる値
println!("合計{}個", stock + arrival); // devビルドはパニック、releaseは4
}Playgroundで開く値がコンパイル時に確定していて必ずオーバーフローする式は、実行を待たずコンパイルエラーになります(deny-by-defaultのarithmetic_overflowリント。Rust 1.93で確認)。上の例が実行時のパニックで済んでいるのは、arrivalが実行時に決まる値だからです。
abortへの切り替え方
切り替えはパッケージのCargo.tomlにあるプロファイルで指定します5。
[profile.release]
panic = "abort"テスト・ベンチマーク・ビルドスクリプト・手続き的マクロはこの設定を無視し、常にunwindでビルドされます5。またabortでビルドすると、パニック時の終了コードは101ではなくプロセス中止のもの(Unix系ではSIGABRTによる134)になります(Rust 1.93で確認)。
パニックメッセージとバックトレース
既定のパニックフック(パニック直後に走る処理)は、メッセージと発生位置を標準エラー出力へ出します1。
thread 'main' (3658200) panicked at src/main.rs:4:28:
index out of bounds: the len is 3 but the index is 5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace1行目の括弧内はスレッドIDです(Rust 1.93での出力)。案内のとおりRUST_BACKTRACE=1を付けて実行すると、どの関数から呼ばれてパニックに至ったかの履歴が表示されます4。フック自体はstd::panic::set_hookで差し替えられます1。
巻き戻しを捕まえるcatch_unwind
std::panic::catch_unwindは、渡した処理の中で起きた巻き戻しを捕まえてResultとして返します6。ただし公式ドキュメントは、これを一般的なtry/catchとして使わないよう明記しています。日常的に失敗しうる処理にはResultが適切で、catch_unwindはFFI境界の外へ巻き戻しを漏らさないといった限られた用途のものです6。panic = "abort"でビルドした場合は、そもそも巻き戻しが起きないため捕まえられません6。
パニック中のパニックは中止になる
巻き戻しの最中に呼ばれたDropがさらにパニックすると、回復のしようがないためpanic in a destructor during cleanupというメッセージとともにプロセスが中止されます7(Rust 1.93で確認)。unwindでビルドしていてもcatch_unwindでは捕まえられません。Dropの実装ではパニックしうる処理を避けるのが原則です。
panic!が持つ型
panic!はその場で実行を打ち切るため、値を返しません。型としてはnever型!を持ち、どんな型が期待される位置にも書けます。そのためmatch式の一部のアームだけをpanic!にしても、アーム全体の型は残りのアームの型に揃います。
fn main() {
let stock = 0;
let n: u32 = match stock {
0 => panic!("在庫がありません"), // 型は!なのでu32のアームと共存できる
n => n,
};
println!("残り{n}個");
}Playgroundで開くFootnotes
-
std::panic — マクロ — 現在のスレッドをパニックさせること、mainスレッドがパニックすると終了コード101で終わること、既定のフックがメッセージと発生位置をstderrへ出すことが記載されています。 ↩ ↩2 ↩3 ↩4
-
The Rust Reference: Panic —
unwindとabortの2戦略、unwindが多くのターゲットで既定であること、巻き戻し中にDropが呼ばれること、境界外の添字アクセスのように自動でパニックする言語構文があることが記載されています。 ↩ ↩2 ↩3 ↩4 -
The Rust Reference: Operator expressions — オーバーフローとみなされる演算の一覧と、0除算・
MIN / -1が検査の有無に関わらずパニックすることが記載されています。 ↩ ↩2 ↩3 -
The Rust Programming Language: Unrecoverable Errors with
panic!↩ ↩2 -
The Cargo Book: Profiles —
panicの指定値、devとreleaseのoverflow-checksの既定値、testプロファイルがdevを継承すること、テストなどがpanic設定を無視することが記載されています。 ↩ ↩2 ↩3 -
std::ops::Drop — 巻き戻し中の
dropがパニックする「二重パニック」ではプログラムが中止されうることが記載されています。 ↩
この辞書が使われているページ
backlinks 15