Result型の使用は、Rustにおけるエラー処理の主要なアプローチの一つです。歴史的に多くの言語—例えばC—では、エラーは特別な戻り値やグローバル変数で通知されることが多く、その結果、これらの信号を無視することによるエラーが頻繁に発生しました。Rustは、Result<T, E>というenumを使用してエラーを明示的に型付けすることで、エラーを偶然に無視することを不可能にしました — コンパイラは成功と失敗の両方の分岐を処理することを強制します。
問題: エラー処理を最大限に安全かつ可読性の高いものにし、「隠れた」エラーを排除し、例外を使用せずにコードの信頼性を向上させる必要がありました。
解決策: Result<T, E>は二つのバリアントを持つ列挙型で、成功時はOk(T)、エラー時はErr(E)を返します。これは、エラーを明示的に処理させるか、unwrapを使用して明示的に無視するか、panicを待つことを強制します。さらに、?演算子は、エラー伝播の一般的なパターンを簡潔にします。
コード例:
use std::fs::File; use std::io::{self, Read}; fn read_file(path: &str) -> Result<String, io::Error> { let mut file = File::open(path)?; let mut contents = String::new(); file.read_to_string(&mut contents)?; Ok(contents) }
主な特徴:
?演算子を介したエラーの連鎖的伝播の簡便性。自動的にエラーを上に伝播させるために?演算子を常に使用できますか?
いいえ、関数のエラータイプが?の右側の式の型と一致する場合にのみ可能です。型が不一致の場合は、.map_err()メソッドを使ってエラーを明示的に変換する必要があります。
コード例:
fn f() -> Result<(), String> { let _f = File::open("foo").map_err(|e| e.to_string())?; Ok(()) }
エラーに興味がない場合、Resultの結果を未使用のままにしておくことはできますか?
いいえ、Result型の結果が処理されないと、コンパイラは警告またはエラーを出します。.unwrap()を呼び出すか、.ok()/.err()/let _ = ...を明示的に呼び出すか、エラーを適切にログしなければなりません。
エラーのあるResultに対して.unwrap()を呼び出すとどうなりますか?
panic!が発生し、プログラムの実行が中断されます。通常、これは異常終了を引き起こします。そのため、unwrap()は成功が保証されている場合にのみ許可されます。
let _ = ...;)?を使用した際のエラータイプの不一致が、混乱したコンパイルエラーを引き起こす開発者はファイルから設定を読み取ることにしたが、読み取り結果に対してunwrap()を使用した。
利点:
欠点:
設定の読み取り中のエラーはログに記録され、Result + ?演算子を使って呼び出しスタックを上に戻されます。
利点:
欠点: