【Rust】がばがばRust独学 - 9. Error Handling
Rustの公式ドキュメントを通して、Rustを独学していく記事になります。(がば要素あり)
今回は、Error Handlingについて学びつつ、気になった点をコード確認した結果を記事にしています。
- versions
$ rustc --version rustc 1.40.0 (73528e339 2019-12-16)
Error Handling
公式ドキュメント(Error Handling - The Rust Programming Language)より、エラーには二種類存在します。
- recoverable : ファイルが見つからないなどのエラー
- unrecoverable :バグなどのエラー
ただ、ほとんどの言語では2種類のエラーを区別せずに、例外を利用して同じ方法で処理します。
Rustには例外は無く、
- recoverable :
Result<T, E>
による回復可能なエラーのタイプ - unrecoverable :
panic!
による実行を停止するマクロ
の二種類が存在します。
Rust groups errors into two major categories: recoverable and unrecoverable errors.
...
Most languages don’t distinguish between these two kinds of errors and handle both in the same way, using mechanisms such as exceptions. Rust doesn’t have exceptions.
Unrecoverable Errors with panic!
Unrecoverable Errors with panic! - The Rust Programming Language
Rustのデフォルトでは、パニックが発生するとプログラムの巻き戻し(unwinding)が開始されて、各関数のデータをクリーンアップします。クリーンアップさせない場合はabortを下記のように設定することにより、巻き戻しが発生しません。巻き戻しを発生させない分、プロジェクトのバイナリを小さくするとが可能ですが、メモリのクリーンアップは自身で実施する必要があります。
[profile.release] panic = 'abort'
※ クリーンアップに関しては別記事でまとめたいと考えております。
panic!
の単純な使用例
fn main() { panic!("crash and burn"); }
thread 'main' panicked at 'crash and burn', src/demo.rs:2:5
panic!
のバックトレースを使用
例えば、下記の場合、実行時エラーより、 RUST_BACKTRACE=1
を環境変数として実行することにより、バックトレースを可能と記載があります。
fn main() { let v = vec![1, 2, 3]; v[99]; }
thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 99', /rustc/73528e339aae0f17a15ffa49a8ac608f50c6cf14/src/libcore/slice/mod.rs:2796:10 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
実際に、 RUST_BACKTRACE=1
を環境変数として実行すると、下記のようにバックトレースしてくれます。
bash-3.2$ RUST_BACKTRACE=1 cargo run --bin demo
Finished dev [unoptimized + debuginfo] target(s) in 0.02s Running `target/debug/demo` thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 99', /rustc/73528e339aae0f17a15ffa49a8ac608f50c6cf14/src/libcore/slice/mod.rs:2796:10 stack backtrace: 0: backtrace::backtrace::libunwind::trace at /Users/runner/.cargo/registry/src/github.com-1ecc6299db9ec823/backtrace-0.3.40/src/backtrace/libunwind.rs:88 1: backtrace::backtrace::trace_unsynchronized at /Users/runner/.cargo/registry/src/github.com-1ecc6299db9ec823/backtrace-0.3.40/src/backtrace/mod.rs:66 2: std::sys_common::backtrace::_print_fmt at src/libstd/sys_common/backtrace.rs:77 3: <std::sys_common::backtrace::_print::DisplayBacktrace as core::fmt::Display>::fmt at src/libstd/sys_common/backtrace.rs:61 4: core::fmt::ArgumentV1::show_usize 5: std::io::Write::write_fmt at src/libstd/io/mod.rs:1412 6: std::sys_common::backtrace::_print at src/libstd/sys_common/backtrace.rs:65 7: std::sys_common::backtrace::print at src/libstd/sys_common/backtrace.rs:50 8: std::panicking::default_hook::{{closure}} at src/libstd/panicking.rs:188 9: std::panicking::default_hook at src/libstd/panicking.rs:205 10: std::panicking::rust_panic_with_hook at src/libstd/panicking.rs:464 11: std::panicking::continue_panic_fmt at src/libstd/panicking.rs:373 12: rust_begin_unwind at src/libstd/panicking.rs:302 13: std::panicking::begin_panic 14: std::panicking::begin_panic 15: <usize as core::slice::SliceIndex<[T]>>::index at /rustc/73528e339aae0f17a15ffa49a8ac608f50c6cf14/src/libcore/slice/mod.rs:2796 16: core::slice::<impl core::ops::index::Index<I> for [T]>::index at /rustc/73528e339aae0f17a15ffa49a8ac608f50c6cf14/src/libcore/slice/mod.rs:2647 17: <alloc::vec::Vec<T> as core::ops::index::Index<I>>::index at /rustc/73528e339aae0f17a15ffa49a8ac608f50c6cf14/src/liballoc/vec.rs:1861 18: demo::main at src/demo.rs:3 19: std::rt::lang_start::{{closure}} at /rustc/73528e339aae0f17a15ffa49a8ac608f50c6cf14/src/libstd/rt.rs:61 20: std::rt::lang_start_internal::{{closure}} at src/libstd/rt.rs:48 21: std::panicking::try::do_call at src/libstd/panicking.rs:287 22: __rust_maybe_catch_panic at src/libpanic_unwind/lib.rs:78 23: std::panicking::try at src/libstd/panicking.rs:265 24: std::panic::catch_unwind at src/libstd/panic.rs:396 25: std::rt::lang_start_internal at src/libstd/rt.rs:47 26: std::rt::lang_start at /rustc/73528e339aae0f17a15ffa49a8ac608f50c6cf14/src/libstd/rt.rs:61 27: demo::main note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
これにより、libcore/slice/mod.rs:2796:10
としてpanicに対して demo::main
の3行目でのエラーと判明させることができます。
18: demo::main at src/demo.rs:3
Recoverable Errors with Result
Recoverable Errors with Result - The Rust Programming Language
Result
は下記のように設定されています。
enum Result<T, E> { Ok(T), Err(E), }
Result
の基本的な使い方
例えば、公式ドキュメントのデモコードから少し改変したコードを利用し、わざと hello.txt
がない場合を出力してみます。そうすると、 Err
として返ってきており、 Os
レベルでのエラーとわかります。
use std::fs::File; fn main() { let f = File::open("hello.txt"); println!("{:#?}", f); }
Err( Os { code: 2, kind: NotFound, message: "No such file or directory", }, )
ファイルが存在するパターンを利用した場合は、下記のように Ok
で返ってきます。
use std::fs::File; fn main() { let f = File::open("Cargo.toml"); println!("{:#?}", f); }
Ok( File { fd: 3, path: "/path/to/Cargo.toml", read: true, write: false, }, )
matchを利用した Result
の制御
Ok
Err
はmatchによって制御可能です。
公式ドキュメントの例より、 hello.txt
が存在しない場合は、ファイルを作成するというものになります。
use std::fs::File; use std::io::ErrorKind; fn main() { let f = File::open("hello.txt"); let file = match f { Ok(file) => file, Err(error) => match error.kind() { ErrorKind::NotFound => match File::create("hello.txt") { Ok(fc) => fc, Err(e) => panic!("Problem creating the file: {:?}", e), }, other_error => panic!("Problem opening the file: {:?}", other_error), }, }; println!("{:#?}", file); }
File { fd: 3, path: "/path/to/hello.txt", read: false, write: true, }
io
の ErrorKind
については、下記のように様々なエラーが用意されております。
unwrap
や expect
のPanicショートカット
unwrap
: メッセージ変更不可expect
: メッセージ変更可
unwrap
を利用した場合、 Err
の場合は Err(E)
の E
を出力してくれます。
use std::fs::File; fn main() { let f = File::open("hello.txt").unwrap(); println!("{:#?}", f); }
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Os { code: 2, kind: NotFound, message: "No such file or directory" }', src/libcore/result.rs:1165:5 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
Ok
の場合の unwrap
は Ok(T)
の T
を返してくれます。
use std::fs::File; fn main() { let f = File::open("Cargo.toml").unwrap(); println!("{:#?}", f); }
File { fd: 3, path: "/path/to/Cargo.toml", read: true, write: false, }
expect
も unwrap
と同様に Err
、 Ok
を返してくれます。
異常系の場合
use std::fs::File; fn main() { let f = File::open("hello.txt").expect("Failed to open hello.txt"); println!("{:#?}", f); }
thread 'main' panicked at 'Failed to open hello.txt: Os { code: 2, kind: NotFound, message: "No such file or directory" }', src/libcore/result.rs:1165:5 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
正常系の場合
use std::fs::File; fn main() { let f = File::open("Cargo.toml").expect("Failed to open Cargo.toml"); println!("{:#?}", f); }
File { fd: 3, path: "/path/to/Cargo.toml", read: true, write: false, }
エラーの伝播
関数の戻り値として Result
を呼び出し元に伝播することが可能です。
fn read_username_from_file() -> Result<String, io::Error>
?
を利用することにより、実行段階での Err
を判別し、すぐさま戻してくれます。
use std::fs::File; use std::io; use std::io::Read; fn main() { let f = read_username_from_file("hello.txt"); println!("{:#?}", f); } fn read_username_from_file(file_name: &str) -> Result<String, io::Error> { let mut s = String::new(); File::open(file_name)?.read_to_string(&mut s)?; Ok(s) }
main
関数でのResultの返し方
main
関数の戻り値はデフォルトで ()
であるため、 Result
を扱いたい場合には下記のように設定することで返すことができます。 Box<dyn Error>
タイプについては、 公式ドキュメント Chapter 17 に記載されているそうです。
use std::error::Error; use std::fs::File; fn main() -> Result<(), Box<dyn Error>> { let f = File::open("hello.txt")?; Ok(()) }
Error: Os { code: 2, kind: NotFound, message: "No such file or directory" }
To panic!
or Not to panic!
To panic! or Not To panic! - The Rust Programming Language
公式ドキュメントより引用すると、仮定・保証・不変式が壊れっる場合はパニックになることをおすすめするとのことです。
It’s advisable to have your code panic when it’s possible that your code could end up in a bad state. In this context, a bad state is when some assumption, guarantee, contract, or invariant has been broken, such as when invalid values, contradictory values, or missing values are passed to your code—plus one or more of the following:
・The bad state is not something that’s expected to happen occasionally.
・Your code after this point needs to rely on not being in this bad state.
・There’s not a good way to encode this information in the types you use.
失敗が予測できる場合は、Result
を利用すべきだとも記載されています。
However, when failure is expected, it’s more appropriate to return a Result than to make a panic! call.
また、値チェックを始めに実施し、おかしければpanicを起こすべきとも記載されています。
When your code performs operations on values, your code should verify the values are valid first and panic if the values aren’t valid.
最後に
長くなってしまいましたが、Error Handlingについて学びました。適切に panic!
を使って動作を止める方が懸命である場合も有るとのことですね・・・
個人的には、web APIとして利用する場合は、 panic!
を引き起こすとアプリケーションが止まってしまい、サービスへの影響が大きいため、 Result
で伝搬して行くほうが懸命だと考えております。
webサービスで panic!
を利用する場合はどんな場合なのでしょうか・・・思いつきません・・・
次回はgenericsですね。型の上限境界、下限境界はあるのかが気になる点です。