Rust勉強手記ーエラーハンドリング
rustのもう一つ特別なところ。エラーハンドリングの設計は他の言語と結構違う面がある。
一般的に使われているtry/catch構造でexceptionをキャッチするロジックは、rustには見えない。
rustでは、回復可能な(recoverable)と回復不可能(unrecoverable)なエラーを区別している。回復不可能な場合は、panic!
マクロが実行され、いわばパニックが発生し、プログラムの実行が中止となる。逆に、回復可能な場合は、Result<T, E>
をリターンする形で、エラーをResult
のEnum
型に箱入りしている。
enum Result<T, E> {
Ok(T),
Err(E),
}
すると、この実行結果によって処理を分けることができるようになる。ある意味で強制的なので、よりrobustなコードになるでしょう。
Enum
型なので、match
と併用する形が思い浮かぶかもしれない。より洗練な書き方、unwrap
やexpect
、クロージャー、?
演算子などもある。
let greeting_file_result = File::open("hello.txt");
let greeting_file = match greeting_file_result {
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);
}
},
};
// よりシンプルに
let greeting_file = File::open("hello.txt").unwrap();
?
を使うときによくエラーのpropagationと関連する。実際の開発にもよくある場面だと思うが、一々全ての関数にtry/catch
を実装するよりも、ミドルウェアとかの形でどこかで統一キャッチしたりする。
rustで実践するときに注意したいのは、caller関数のリターンタイプと、?
演算のリターンタイプの一致性だと。例えばmain関数のなかで全てのエラーを「キャッチ」しようとすると、
use std::fs::File;
fn main() {
let greeting_file = File::open("hello.txt")?;
}
this function should return Result or Option to accept ?
とのエラーメッセージが出てしまう。このときに、File::open
関数のリターン値はResult
型なので、main
関数も合わせる必要がある。
use std::error::Error;
use std::fs::File;
fn main() -> Result<(), Box<dyn Error>> {
let greeting_file = File::open("hello.txt")?;
Ok(())
}
この辺りまだ不慣れがあってもう少し時間がかかりそう。
それでいつpanic!
を使うか、いつResult<T, E>
を使うか、との問題が出てくる。
これはtsで言えば、try/catch
でcatchブロックでunknown
型エラーに対してエラーの種類を一個ずつ確認して、それぞれの処理を行う場面を考えるとわかりやすいかもしれない。一般的に、この失敗・エラーは想定中の可能性の一つ、Result
で考えた方が良いらしい。
もしくはコンパイラーより多くの情報を持つ場合、例えばreactのDOM操作で!
をつけて要素が必ず存在するよという時はコンパイラーより開発者が多くの情報を持っている→大丈夫だとと確信を持っている時、Result
も使った方が良いみたい。
ただし、コードがbad state にある時は、パニックを採用する。この悪い状態というのは、公式でwhen some assumption, guarantee, contract, or invariant has been broken
と説明している。こちらのポストにもあるように、a fundamental assumption of the current code block is found to be false
の状態。言い換えれば、コードが予定通り、正しく実行できない状態とも解釈できる。
この辺りは以前設計原則のところと少し関係あるが、例えば契約的設計の前置き条件・後置き条件が破れたときは該当するでしょう。これらの条件は「コードが正しく実行する」ために設置されていると想定できるので、bad stateの基準にもなりそうかなと。
また、意図的にパニックしたいケースもあったりする。例えばテストの時、prototyping(ここは若干意味理解していないが、設計パターンのことかな)、ただただサンプルコードを書いている時など。