Rustは明示的なエラーハンドリングに基づいて構築されています:例外は存在せず、代わりにResult<T, E>という戻り値が使用されます。これにより、コードの安全性と予測可能性が確保されます。
問題の背景:
多くの言語は例外の道を歩んできたため、予期しない状況や実行時に例外を明示的に処理する必要が生じました。Rustは最初から型による制御を重視し、すべてのエラーは関数のシグネチャの一部でなければなりません。
問題点:
主な課題は、エラーが無視されず隠されないようにコードを書くことです。「クラッシュ」する関数はなく、同時にコードがコンパクトで可読性を保つ必要があります。情報を失わず、論理を混乱させずにエラーを上位に伝播させる必要があります。
解決策:
重要なツールはResult<T, E>型と?オペレーターであり、これは結果を自動的に「アンラップ」します:エラーが発生した場合、関数から即座に退出しエラーを返し、成功した場合は値を返します。
コード例:
fn read_number(file: &str) -> Result<i32, std::io::Error> { let content = std::fs::read_to_string(file)?; let num: i32 = content.trim().parse()?; Ok(num) }
主な特徴:
?オペレーターによってネストを簡略化(if-let/unwrap/expectを除去).map_err()や他のメソッドを通じて簡単に「ラップ」または変換可能ユニット(void)を返す関数で?を使用できますか?
いいえ、?オペレーターはResultまたはOptionを返す関数内でのみ許可されます。関数が()を返す場合、?を使用することはできません。
複数の呼び出しでエラータイプが異なる場合、どうなりますか?
コンパイルエラーになります:エラータイプは明確に定義されている必要があります。すべてのエラーを.map_err()を使用して統一するか、thiserrorを使用するか、APIレベルでenumラッパーを記述する必要があります。例:
fn foo() -> Result<_, MyError> { let a = bar()?; let b = baz().map_err(MyError::Baz)?; Ok(…) }
内部ロジックでの.unwrap()の危険性は?
「主要な」コード内でunwrap()を安心して使用できるという誤解が広まっていますが、「絶対にクラッシュしない」という保証はありません。実際には、小さな目に見えないエラーでも実行時パニックを引き起こし、プログラムの安全性が損なわれます。
unwrap/expectでキャッチすることmap_err(|_| …)におけるコンテキスト喪失、エラーが元の情報を保持せずに閉じられること?なしで冗長なネストを生成することプロダクションコードで迅速なデバッグの後、多くのunwrap()が残されました。その結果、ユーザーの不正な入力によるパースのどんな失敗でもアプリケーションがクラッシュしました。
利点:
欠点:
完全なResult<T, E>スタックを使用し、エラーを明示的に記述し、情報を保持しつつ?と.map_err()のみを使用しました。
利点:
欠点: