Result 型とは?
Rust の標準ライブラリには、std::result::Result という列挙型 (enum) が用意されており、何らかの処理が「成功」したこと、あるいは「失敗」したことを表現するために使われます(仕組み的には、値の「有無」を表現する Option 型 と同様です)。
他の言語では、例外 (exception) の仕組みでエラーの発生を表現したりしますが、Rust では Result
型を使ってエラーを表現します(Rust には例外の仕組みが存在しません)。
Result
列挙型のバリアントとしては、次のように Ok
と Err
だけが定義されており、それぞれが何らかの処理の「成功」と「失敗」を表現します。
成功と失敗を表すだけであれば、bool
型だけで表現できそうですが、Result
型は Ok
と Err
というバリアントが任意の値を持つことができるようになっているので、成功した場合の結果や、失敗した場合の理由を表現することができます。
また、何らかのメソッドが Result
型を返すとき、その値を呼び出し側で利用していないと、コンパイラが警告を出してくれるため、エラーのハンドル忘れを防ぐ効果 があります。
Result
型の概念は、他のモダンな言語でも採用されています(例: Kotlin の Result 型)。
これらのシンボルはデフォルトでインポートされるようになっており、Result
、Ok
、Err
と記述するだけで使用することができます(Result::Ok
や Result::Err
のように記述する必要はありません)。
Result を処理する
Result
インスタンスから Ok
バリアントや Err
バリアントの情報を取り出すには、match
式や if let
式を使います。
以下のサンプルコードでは、str::parse
メソッドで、数値を含む文字列をパースして i32
値に変換しています。
パースに成功すると Ok
バリアントが返されるので、そこからパース結果を取り出すことができます。
パースに失敗すると Err
バリアントが返されるので、そこからエラー情報(ParseIntError
など)を取り出すことができます。
match 式
Result
列挙型のバリアント(Ok
と Err
)を漏れなくハンドルしたいときは、match
式を使用します。
let result = "123".parse::<i32>();
match result {
Ok(num) => println!("数値 {} としてパースできました", num),
Err(err) => println!("パースできませんでした: {}", err),
}
if let 式
処理の成功時に Ok
バリアントが持つ値を取り出すだけでよければ、if let
構文を使うと簡潔に記述できます。
let result = "123".parse::<i32>();
if let Ok(num) = result {
println!("数値 {} としてパースできました", num);
}
is_ok / is_err メソッド
処理に成功したか、失敗したかだけを確認するには、is_ok
や is_err
メソッドを使用します。
if result.is_ok() {
println!("成功しました");
}
if result.is_err() {
println!("失敗しました");
}
unwrap 系メソッド
Ok
バリアントが含むデータをダイレクトに取り出したいときは、unwrap
系のメソッドを使用します。
ただし、unwrap
メソッドは Result
の値が Err
のときに呼び出すとパニックが発生するので、ほとんどのケースでは使用を避けるべきです(即席の使い捨てプログラムを作るときは便利ですが)。
let result = "ほげ".parse::<i32>();
// 成功時は i32 値を返し、失敗時はパニックが発生する
let num = result.unwrap();
// unwrap の代わりに expect を使うと、パニック時のメッセージを指定できる
let num = result.expect("Failed to parse");
unwrap
の代わりに unwrap_or
メソッドを使用すると、Err
のときに使用する代替値を指定できます。
こちらはパニックが発生しないので安全です。
let num = result.unwrap_or(0); // パースに失敗したときは 0 が返される
let num = result.unwrap_or_default(); // 同上(整数型のデフォルト値 0 が使われる)
代替値の生成にコストがかかる場合は、unwrap_or_else
メソッドを使って代替値を生成する関数を渡すようにします。
この関数は Err
時にしか呼び出されません。
次の例では、クロージャの形で代替値の生成処理を指定しています。
// パースに失敗したときは代替値生成用の関数を呼び出す
let num = result.unwrap_or_else(|_| create_default_value());
// 代替値を生成する関数(本当はコストのかかる処理という想定)
fn create_default_value() -> i32 { 999 }
クロージャにはエラー値(この場合は ParseIntError
)が渡されますが、今回は使わないのでアンダースコア (_
) で受け取って無視しています。
Result 型を返す関数の実装例
下記の divide
関数は、整数値の割り算を行う関数です。
計算に成功すると、その結果 (i32
) を含む Result::Ok
を返し、計算に失敗すると、エラーメッセージ (&str
) を含む Result::Err
を返します。
fn divide(numerator: i32, denominator: i32) -> Result<i32, &'static str> {
if denominator == 0 {
return Err("0 で割ることはできません");
}
Ok(numerator / denominator)
}
match divide(5, 0) {
Ok(num) => println!("割り算の結果: {}", num),
Err(err) => println!("エラー発生: {}", err),
}
Result::Ok
バリアントに値を設定する必要がないときは、値がないことを示す空タプル ()
を使って、Ok(())
を返すようにします。
main()
関数の実装でよく見ると思います。
fn perform(action: &str) -> Result<(), &'static str> {
if action == "dance" {
Ok(())
} else {
Err("実行できませんでした")
}
}
let result = perform("sing");
match result {
Ok(_) => println!("実行完了"),
Err(err) => println!("エラー: {}", err),
}
Result 型でエラーのハンドル忘れを防ぐことができるのはなぜか?
Result
型の定義には、次のように #[must_use]
アノテーションが付いているため、何らかのメソッドが戻り値として Result
を返したとき、その値を無視できないようになっています(参考: Result 型のコード)。
何らかのメソッドが返した Result
を、match
や if let
で「消費」することで、この警告は表示されなくなります。