Rust のエラーハンドリング
分類#
- 大きく分けて2つ
- 回復不可能なエラー:
panic!マクロを用いる- 配列の境界を超えた箇所にアクセスしようとすることなど
- プログラムを強制的に終わらせる
- 回復可能なエラー:
Result<T, E>を用いる- ファイルが見つからないなどの回復可能なエラーには、問題をユーザに報告し、処理を再試行する
- 回復不可能なエラー:
panic! で回復不可能なエラーを起こす#
panic!マクロが実行されると、プログラムは失敗のメッセージを表示し、終了する。- 標準では、プログラムはスタックの巻き戻しを行う。
- スタックを遡り、 遭遇した各関数のデータを片付ける。
- しかし、これではやることが多くなる場合があるので、オプションとして、データの片付けをせずにプログラムを異常終了させる方法も用意されている。
- 結果として、プロジェクトの実行可能ファイルは小さくなる。
- ただし、この場合、スタックに残ったデータの処理は OS で行う必要がある。
- このオプションを選択する場合は、
Cargo.tomlの[profile]欄にpanic = 'abort'を追記する下記の例は、リリースモードにて異常終了を選択する場合の記述
- 標準では、プログラムはスタックの巻き戻しを行う。
これが起こる場合の大半は、 何らかのバグが検出された時であり、プログラマ対して対処方法が明確ではない場合。
実際に自分で起こす場合は以下のようにする:
これを実行すると、以下のようなエラーメッセージが発生:
1$ cargo run 2 Compiling panic v0.1.0 (file:///projects/panic) 3 Finished dev [unoptimized + debuginfo] target(s) in 0.25 secs 4 Running `target/debug/panic` 5thread 'main' panicked at 'crash and burn', src/main.rs:2:4 6('main'スレッドはsrc/main.rs:2:4の「クラッシュして炎上」でパニックしました) 7note: Run with `RUST_BACKTRACE=1` for a backtrace.
ライブラリで実装されている
panic!を起こしたのが以下の例:ベクタの境界を超えた要素へのアクセスを試みたこれを実行すると以下のメッセージが出る:
1$ cargo run 2 Compiling panic v0.1.0 (file:///projects/panic) 3 Finished dev [unoptimized + debuginfo] target(s) in 0.27 secs 4 Running `target/debug/panic` 5thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 699', /checkout/src/liballoc/vec.rs:1555:10 7('main'スレッドは、/checkout/src/liballoc/vec.rs:1555:10の 8「境界外番号: 長さは3なのに、添え字は99です」でパニックしました) 9note: Run with `RUST_BACKTRACE=1` for a backtrace.- このエラーは、自分のファイルではない
vec.rs(標準ライブラリのVec<T>の実装)ファイルを指している。- ベクタ
vに対して[]を使った時に走るコードは、vec.rsに存在し、ここで実際にpanic!が発生している。
- ベクタ
- このエラーは、自分のファイルではない
エラー発生に至るまでに呼び出された全関数を見るバックトレースを得るには、環境変数
RUST_BACKTRACEを 0 以外に設定する- 実際に吐き出されるバックトレースは以下のような感じ
1$ RUST_BACKTRACE=1 cargo run 2 Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs 3 Running `target/debug/panic` 4thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 99', /checkout/src/liballoc/vec.rs:1555:10 5stack backtrace: 6 0: std::sys::imp::backtrace::tracing::imp::unwind_backtrace 7 at /checkout/src/libstd/sys/unix/backtrace/tracing/gcc_s.rs:49 8 1: std::sys_common::backtrace::_print 9 at /checkout/src/libstd/sys_common/backtrace.rs:71 10 2: std::panicking::default_hook::{{closure}} 11 at /checkout/src/libstd/sys_common/backtrace.rs:60 12 at /checkout/src/libstd/panicking.rs:381 13 3: std::panicking::default_hook 14 at /checkout/src/libstd/panicking.rs:397 15 4: std::panicking::rust_panic_with_hook 16 at /checkout/src/libstd/panicking.rs:611 17 5: std::panicking::begin_panic 18 at /checkout/src/libstd/panicking.rs:572 19 6: std::panicking::begin_panic_fmt 20 at /checkout/src/libstd/panicking.rs:522 21 7: rust_begin_unwind 22 at /checkout/src/libstd/panicking.rs:498 23 8: core::panicking::panic_fmt 24 at /checkout/src/libcore/panicking.rs:71 25 9: core::panicking::panic_bounds_check 26 at /checkout/src/libcore/panicking.rs:58 27 10: <alloc::vec::Vec<T> as core::ops::index::Index<usize>>::index 28 at /checkout/src/liballoc/vec.rs:1555 29 11: panic::main 30 at src/main.rs:4 31 12: __rust_maybe_catch_panic 32 at /checkout/src/libpanic_unwind/lib.rs:99 33 13: std::rt::lang_start 34 at /checkout/src/libstd/panicking.rs:459 35 at /checkout/src/libstd/panic.rs:361 36 at /checkout/src/libstd/rt.rs:61 37 14: main 38 15: __libc_start_main 39 16: <unknown>
Result で回復可能なエラーを起こす#
Resultenum はOkとErrの2つから成るTとEはジェネリクス
使い方は以下のような感じ
1use std::fs::File; 2 3fn main() { 4 // let f: u32 = File::open("hello.txt"); と書くとコンパイルエラー 5 // 帰ってくる型は std::result::Result 6 let f = File::open("hello.txt"); 7 8 let f = match f { 9 Ok(file) => file, 10 Err(error) => { 11 // ファイルを開く際に問題がありました 12 panic!("There was a problem opening the file: {:?}", error) 13 }, 14 }; 15}
色々な種類のエラーを異なる方法で扱う#
1use std::fs::File;
2use std::io::ErrorKind;
3
4fn main() {
5 let f = File::open("hello.txt");
6
7 let f = match f {
8 Ok(file) => file,
9 Err(ref error) if error.kind() == ErrorKind::NotFound => {
10 match File::create("hello.txt") {
11 Ok(fc) => fc,
12 Err(e) => {
13 panic!(
14 //ファイルを作成しようとしましたが、問題がありました
15 "Tried to create file but there was a problem: {:?}",
16 e
17 )
18 },
19 }
20 },
21 Err(error) => {
22 panic!(
23 "There was a problem opening the file: {:?}",
24 error
25 )
26 },
27 };
28}if error.kind() == ErrorKind::Notfoundという条件式は、マッチガードと呼ばれる- アームのパターンをさらに洗練する
matchアーム上のおまけの条件式
- アームのパターンをさらに洗練する
エラー時にパニックするショートカット#
unwrap:ResultがOkの場合は中身の型を返す、Errの場合はpanic!を起こすファイルが見つからない場合は以下のようなメッセージが出力される
1thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Error { 2repr: Os { code: 2, message: "No such file or directory" } }', 3src/libcore/result.rs:906:4 4('main'スレッドは、src/libcore/result.rs:906:4の 5「`Err`値に対して`Result::unwrap()`が呼び出されました: Error{ 6repr: Os { code: 2, message: "そのようなファイルまたはディレクトリはありません" } }」でパニックしました)
expect:unwrapと基本的に同じだが、panic!のメッセージ内容も記述可能エラーメッセージは以下のように成る
エラーを委譲する#
関数内ではなく、関数の呼び出し元にエラーをどうするかを決めてもらう( 委譲 )
1use std::io; 2use std::io::Read; 3use std::fs::File; 4 5fn read_username_from_file() -> Result<String, io::Error> { 6 let f = File::open("hello.txt"); 7 8 let mut f = match f { 9 Ok(file) => file, 10 Err(e) => return Err(e), 11 }; 12 13 let mut s = String::new(); 14 15 match f.read_to_string(&mut s) { 16 Ok(_) => Ok(s), 17 Err(e) => Err(e), 18 } 19}- 1つめのファイル呼び出しが失敗しても、2つめの文字読み込みが失敗しても、呼び出し元の関数にエラーを委譲する
エラー委譲のショートカット: ? 演算子#
上記の処理は以下のように書ける
もっと省略するとこう。
?演算子の連結:?を利用することで、定型コードの多くを排除できるただし、
?演算子が使えるのは、returnする型がResultの関数のみ以下はエラーとなる:
エラーメッセージはこんな感じ:
1error[E0277]: the trait bound `(): std::ops::Try` is not satisfied 2(エラー: `(): std::ops::Try`というトレイト境界が満たされていません) 3 --> src/main.rs:4:13 4 | 54 | let f = File::open("hello.txt")?; 6 | ------------------------ 7 | | 8 | the `?` operator can only be used in a function that returns 9 `Result` (or another type that implements `std::ops::Try`) 10 | in this macro invocation 11 | (このマクロ呼び出しの`Result`(かまたは`std::ops::Try`を実装する他の型)を返す関数でしか`?`演算子は使用できません) 12 | 13 = help: the trait `std::ops::Try` is not implemented for `()` 14 (助言: `std::ops::Try`トレイトは`()`には実装されていません) 15 = note: required by `std::ops::Try::from_error` 16 (注釈: `std::ops::Try::from_error`で要求されています)
ここに掲載する記事はまだありません。
