Result::unwrap() / expect() / ? 演算子
| 対応: | Rust 1.0(2015) |
|---|
『Result』から値を取り出すメソッドと、エラーを自動的に呼び出し元へ伝播させる『?』演算子の使い方です。特に『?』演算子はエラー処理のボイラープレートを大幅に削減できます。
構文
// Okなら値を返し、Errならパニックします。
let value = result.unwrap();
// Errのとき指定メッセージでパニックします。
let value = result.expect("エラーメッセージ");
// Errのときデフォルト値を返します。
let value = result.unwrap_or(デフォルト値);
// Errのとき呼び出し元にエラーを伝播します(?演算子)。
// Result型を返す関数の中でのみ使用できます。
let value = some_func()?;
メソッド一覧
| メソッド・演算子 | 概要 |
|---|---|
| unwrap() | 『Ok(x)』なら『x』を返します。『Err』の場合はパニックします。プロトタイプやテストコード以外での使用はpanicのリスクがあります。 |
| expect("msg") | 『unwrap()』と同じですが、パニック時に指定メッセージが表示されます。原因の特定がしやすくなります。 |
| unwrap_or(val) | 『Err』のときにデフォルト値を返します。エラーを無視して処理を継続したい場合に使います。 |
| unwrap_or_else(fn) | 『Err(e)』のときにエラー値を受け取るクロージャを実行します。エラーの内容に応じたデフォルト値を返せます。 |
| ? 演算子 | 『Ok(x)』なら『x』を返し、『Err(e)』なら即座に呼び出し元へエラーを返します。関数がResult(またはOption)を返す場合のみ使用できます。 |
| ok() / err() | ResultをOptionに変換します。『ok()』は『Ok(x)』→『Some(x)』、『Err』→『None』に変換します。 |
サンプルコード
『unwrap』と『?』演算子を使った『Result』のエラー伝播を確認するサンプルコードです。
sample_result_unwrap_question.rs
use std::num::ParseIntError;
// ?演算子でエラーを呼び出し元に伝播します。
fn parse_and_double(s: &str) -> Result<i32, ParseIntError> {
let n = s.parse::<i32>()?; // パース失敗時は即座にErrを返します。
Ok(n * 2)
}
// ?を複数回使ったチェーン例です。
fn parse_sum(a: &str, b: &str) -> Result<i32, ParseIntError> {
let x = a.parse::<i32>()?;
let y = b.parse::<i32>()?;
Ok(x + y)
}
fn main() {
// ?演算子を使った関数の呼び出しです。
println!("{:?}", parse_and_double("5")); // Ok(10)
println!("{:?}", parse_and_double("abc")); // Err(...)
println!("{:?}", parse_sum("3", "4")); // Ok(7)
println!("{:?}", parse_sum("3", "x")); // Err(...)
// unwrap_orでエラー時にデフォルト値を使います。
let n = "abc".parse::<i32>().unwrap_or(0);
println!("{}", n); // 0
// unwrap_or_elseでエラー内容を利用します。
let n = "abc".parse::<i32>().unwrap_or_else(|e| {
println!("パースエラー: {}", e);
-1
});
println!("{}", n); // -1
// ok()でResultをOptionに変換します。
let opt = "42".parse::<i32>().ok();
println!("{:?}", opt); // Some(42)
}
コンパイルして実行すると次のように出力されます。
rustc result_unwrap_question.rs
./result_unwrap_question
Ok(10)
Err(ParseIntError { kind: InvalidDigit })
Ok(7)
Err(ParseIntError { kind: InvalidDigit })
0
パースエラー: invalid digit found in string
-1
Some(42)
よくあるミス
よくあるミス1: ?演算子をvoid戻り値の関数で使ってコンパイルエラー
?演算子は関数の戻り値がResult(またはOption)である必要があります。main()で使う場合は戻り値の型をResult<(), Box<dyn std::error::Error>>に変更します。
unwrap_mistake1.rs
use std::num::ParseIntError;
// 戻り値が()の関数で?は使えません。
fn parse_number(s: &str) {
let n = s.parse::<i32>()?; // コンパイルエラー
println!("{}", n);
}
fn main() {
parse_number("42");
}
rustc unwrap_mistake1.rs error[E0277]: the `?` operator can only be used in a function that returns `Result` or `Option`
Result型を返す関数にするか、?の代わりにmatchを使います。
unwrap_fix1.rs
use std::num::ParseIntError;
// Result型を返す関数に変更します。
fn parse_number(s: &str) -> Result<(), ParseIntError> {
let n = s.parse::<i32>()?;
println!("{}", n);
Ok(())
}
// mainでも?を使う場合はResult型にします。
fn main() -> Result<(), Box<dyn std::error::Error>> {
parse_number("42")?;
parse_number("99")?;
Ok(())
}
rustc unwrap_fix1.rs ./unwrap_fix1 42 99
よくあるミス2: expect()メッセージが不十分でデバッグ困難
expect()を使うときはパニック時にデバッグに役立つ具体的なメッセージを指定します。「should succeed」のような曖昧なメッセージは原因の特定を困難にします。
unwrap_expect.rs
use std::env;
fn main() {
// 曖昧なメッセージではデバッグが難しくなります。
// let port = env::var("PORT").expect("should succeed");
// 具体的なメッセージを付けます。
let port = env::var("PORT").unwrap_or_else(|_| String::from("8080"));
println!("PORT={}", port);
// テストコードではexpect()のわかりやすいメッセージが有効です。
let n = "42".parse::<i32>().expect("テスト用の定数値なのでパースが失敗することはない");
println!("n={}", n);
}
rustc unwrap_expect.rs ./unwrap_expect PORT=8080 n=42
概要
『?』演算子はRust 1.13で導入されたシンタックスシュガーで、『match』による手動のエラー伝播コードを1文字に短縮できます。『?』は関数の戻り値が『Result』または『Option』でないと使用できません。『main()』関数で使う場合は戻り値を『Result<(), Box<dyn std::error::Error>>』にする必要があります。
エラーの型変換が必要な場合、『?』は自動的に『From』トレイトを呼び出してエラー型を変換します。そのためカスタムエラー型に適切な『From』実装があれば、異なるエラー型が混在する関数でも『?』を統一的に使えます。
記事の間違いや著作権の侵害等ございましたらお手数ですがこちらまでご連絡頂ければ幸いです。