Rustのpanic
パニック(panic)は、取り返しのつかない問題が起きたときに、Rustがプログラムをすぐに止める仕組みです。これまでのページを試してきた人は、実はもう何度か知らないうちにパニックを起こしているかもしれません。配列の範囲外にアクセスしたり、None に .unwrap() を呼んだりすると、どちらもパニックします。
自分でパニックを起こす
Section titled “自分でパニックを起こす”fn main() { panic!("深刻な問題が発生しました");}thread 'main' (12345) panicked at src/main.rs:2:5:深刻な問題が発生しましたnote: run with `RUST_BACKTRACE=1` environment variable to display a backtrace括弧内の数字(ここでは (12345))はスレッドIDです。実行するたびに変わり、古いバージョンのRustでは表示されません。
これを実行すると、プログラムはメッセージを表示して止まります。panic! より後のコードは何も実行されません。1行目の thread 'main' (12345) panicked at ... は「メインスレッドが src/main.rs の2行目・5列目でパニックした」という意味で、最後の note: は「環境変数 RUST_BACKTRACE=1 を付けて実行すると、バックトレース(どこから呼ばれたかの履歴)が表示される」という意味です。どちらもRust自身が出すメッセージなので英語のままです。
panic!を直接呼ばなくてもパニックする場面
Section titled “panic!を直接呼ばなくてもパニックする場面”これまでに出てきたいくつかの操作は、問題があると自動的にパニックします。下の例では、危ない値をコードに直接書くのではなく、関数の引数経由で渡しています。インデックスや 0 をリテラルでその場に書くと、コンパイラはその操作が必ず失敗することをコンパイル時に見抜けることが多く、実行してパニックさせる前に、そもそもビルドを拒否してしまうからです。
fn get(numbers: &[i32], index: usize) -> i32 { numbers[index] // パニック: インデックスが範囲外}
fn main() { let numbers = [1, 2, 3]; println!("{}", get(&numbers, 10));}fn main() { let value: Option<i32> = None; println!("{}", value.unwrap()); // パニック: called `Option::unwrap()` on a `None` value}fn divide(a: i32, b: i32) -> i32 { a / b // パニック: attempt to divide by zero(0で割ろうとした)}
fn main() { let a = 10; let b = 0; println!("{}", divide(a, b));}どれも、意味をなさない状態のままプログラムを続けさせず、こっそりおかしな値を返すこともしない、というRustの姿勢の表れです。
panic!とResult
Section titled “panic!とResult”ここが、これから何度も迫られる本当の判断です。この失敗でプログラムを止めるべきか、それとも呼び出す側に対処するチャンスを与えるべきか。
panic!:バグや、コードが正しければ起こるはずのない状況のためのものです。まともに回復する方法がないので、(被害が広がる前に)すぐに止めるのがいちばん安全です。Result(前のページで紹介しました):不正なユーザー入力、ファイルが見つからない、ネットワーク通信の失敗など、呼び出す側が想定していて対処したいと思うのが自然な失敗のためのものです。
// ユーザーからの不正な入力。回復できるのでResultを使うfn parse_age(input: &str) -> Result<u32, String> { input.parse().map_err(|_| String::from("正しい年齢ではありません"))}
// コードが正しければありえない前提条件。panicするfn get_first(numbers: &[i32]) -> i32 { if numbers.is_empty() { panic!("get_firstが空のスライスで呼ばれました"); } numbers[0]}unwrap()とexpect()はパニックへの近道
Section titled “unwrap()とexpect()はパニックへの近道”Option でも Result でも .unwrap() を見てきました。これは実は「値をちょうだい。なければパニックして」の省略形にすぎません。.expect("メッセージ") も同じことをしますが、もっと役に立つメッセージを添えられます。
fn main() { let input = "42"; let number: i32 = input.parse().expect("inputは正しい数値のはずです"); println!("{number}");}変換に失敗すると、パニックのメッセージにあなたが書いたテキストが含まれます。.unwrap() が出す一般的なメッセージよりも、デバッグがずっと楽になります。
前提をチェックする:assert!、assert_eq!、unreachable!
Section titled “前提をチェックする:assert!、assert_eq!、unreachable!”コードのある地点で「ここでは絶対にこうなっているはず」とわかっていることがあります。Rust には、その思い込みをチェックに変えてくれる小さなマクロがいくつかあります。チェックに失敗すると、わかりやすいメッセージ付きでパニックします。そのため、バグがあとで奇妙な結果を引き起こすのではなく、バグが始まったその場所で見つかります。
assert!(条件, "メッセージ")は、条件がfalseならパニックします。assert_eq!(left, right)は、2つの値が等しくなければパニックし、両方の値を表示します。unreachable!("メッセージ")は、絶対に実行されないはずのコードに印を付けます。ありえない値に対するmatchのアームなどです。
fn average(numbers: &[i32]) -> i32 { assert!(!numbers.is_empty(), "average には少なくとも1つの数が必要です");
let mut total = 0; for n in numbers { total += n; } total / numbers.len() as i32}
fn day_name(day: u8) -> &'static str { match day { 1 => "月曜日", 2 => "火曜日", 3 => "水曜日", 4 => "木曜日", 5 => "金曜日", 6 => "土曜日", 7 => "日曜日", _ => unreachable!("day は1から7のはずですが、{day} でした"), }}
fn main() { let avg = average(&[70, 80, 90]); assert_eq!(avg, 80); println!("平均: {avg}");
println!("3日目は{}", day_name(3));}実行結果:
平均: 803日目は水曜日ここではすべてのチェックが通るので、プログラムはふつうに動きます。では、Playground でリストを [70, 80, 95] に変えてみてください。平均が81になり、assert_eq! が2つの値を並べて表示してプログラムを止めます。
thread 'main' (12345) panicked at src/main.rs:26:5:assertion `left == right` failed left: 81 right: 80「left == right というアサーションが失敗した」という意味で、left(左側の値)が81、right(右側の値)が80だったことがわかります。
average(&[]) や day_name(9) も試してみましょう。パニックの中に自分で書いたメッセージが表示されます。
これらのチェックはリリースビルドでも必ず実行されるので、軽い処理にしておきましょう。assert_eq! にはテストのページでまた出会います。テストが「これはあれと等しいはず」と確かめるための、いちばん基本的な方法です。ほかに todo!() もあります。これは “not yet implemented”(まだ実装されていません)というメッセージでパニックします。関数の骨組みだけ先に書いておき、中身はあとで仕上げたいときの仮置きとして便利です。
1. expectでポート番号を読む
Section titled “1. expectでポート番号を読む”設定値 let port_text = "8080"; があります。.parse() と .expect(...) を使ってこれを u16 に変換してください。メッセージには、何がまずかったのかがわかる説明を書きます。そのあと ポート8080で待ち受け中 と表示しましょう。ヒント:parse が何を作ればいいのかわかるように、変数に型を付けます(let port: u16 = ...)。うまくいったら、テキストを "80a0" に変えて、パニックに自分のメッセージが出るのを確かめてみましょう。
解答を見る
fn main() { let port_text = "8080"; let port: u16 = port_text .parse() .expect("設定ファイルのポートは0から65535までの数値のはずです"); println!("ポート{port}で待ち受け中");}実行結果:
ポート8080で待ち受け中2. 割り勘
Section titled “2. 割り勘”ひとりあたりの金額を返す fn split_bill(total: u32, people: u32) -> u32 を書いてください。0人で割り勘するのは呼び出し側のコードのバグなので、わかりやすいメッセージ付きの assert! で捕まえます。合計120を4人で割ったときの金額を表示しましょう。ヒント:割り算の前、関数の1行目に assert! を置きます。
解答を見る
fn split_bill(total: u32, people: u32) -> u32 { assert!(people > 0, "split_bill には少なくとも1人が必要です"); total / people}
fn main() { let share = split_bill(120, 4); println!("ひとりあたり{share}");}実行結果:
ひとりあたり30panic!は、エラーメッセージを表示してプログラムをすぐに止めます。- 標準の操作の中には、自動的にパニックするものがあります(配列の範囲外アクセス、
Noneへの.unwrap()、0による割り算など)。 - ありえないはずのバグや前提条件の破れには
panic!を、呼び出す側が対処できる失敗にはResultを使いましょう。 .expect("メッセージ")は.unwrap()と同じですが、パニックをデバッグしやすくするメッセージを付けられます。assert!、assert_eq!、unreachable!は、正しいと信じていたことが間違っていたときに、わざとパニックを起こします。
理解度チェック
Section titled “理解度チェック”理解度チェック
1. panic! を呼ぶとどうなりますか?
2. このページの目安によると、Resultではなくpanic!を使うべきなのはどんなときですか?
3. .unwrap() と .expect("メッセージ") の違いは何ですか?
スコア: 0 / 3
さらに詳しく
Section titled “さらに詳しく”- panic!で回復不能なエラー — The Rust Programming Language 日本語版:パニックのときに何が起きるのか、巻き戻し(unwinding)、バックトレースの読み方を説明しています。
- panic!すべきかするまいか — The Rust Programming Language 日本語版:
panic!とResultを使い分けるための公式の指針を、例と一緒に紹介しています。 - panic! — Rust By Example 日本語版:パニックの短い例です。プログラムが巻き戻されるときにも、メモリがきちんと解放されることがわかります。
assert_eq!— 標準ライブラリのドキュメント(英語):独自のメッセージの付け方も含めた、アサーション用マクロの仕組みです。panic!— 標準ライブラリのドキュメント(英語):panic!そのもののリファレンスです。unwrapとの関係や、パニックが正しい選択になるのはどんなときかがわかります。