Result<T, E> / Ok / Err
| 対応: | Rust 1.0(2015) |
|---|
『Result<T, E>』は処理の成功(『Ok』)と失敗(『Err』)を型で表す列挙型です。Rustでは例外の代わりに『Result』を使うことで、エラーの可能性をコンパイル時に強制的に処理させます。
構文
// 成功値をOkで包みます。
let ok: Result<i32, String> = Ok(42);
// エラー値をErrで包みます。
let err: Result<i32, String> = Err(String::from("エラーが発生しました。"));
// matchで成功・失敗を分岐します。
match result {
Ok(value) => println!("成功: {}", value),
Err(e) => println!("失敗: {}", e),
}
// if letで成功時だけ処理します。
if let Ok(value) = result {
println!("成功: {}", value);
}
構文一覧
| 構文・バリアント | 概要 |
|---|---|
| Ok(値) | 処理が成功したことを表すバリアントです。成功時の結果値を保持します。 |
| Err(エラー値) | 処理が失敗したことを表すバリアントです。エラーの詳細情報を保持します。エラー型は任意の型を使えます。 |
| match | 『Ok(v)』と『Err(e)』の2パターンで分岐します。両パターンの処理を強制するためエラーの見落としを防ぎます。 |
| if let Ok(v) | 成功時だけ処理するシンタックスシュガーです。エラーを無視するため、乱用するとエラーの見落としにつながります。 |
| is_ok() / is_err() | 結果が成功か失敗かを真偽値で返します。処理は行わず判定だけしたい場合に使います。 |
サンプルコード
『Result』型の『Ok』と『Err』を使ったエラーハンドリングの基本を確認するサンプルコードです。
sample_result_ok_err.rs
// 割り算を行い、0除算はErrで返す関数です。
fn divide(a: f64, b: f64) -> Result<f64, String> {
if b == 0.0 {
Err(String::from("0で割ることはできません。"))
} else {
Ok(a / b)
}
}
fn main() {
// 成功ケースをmatchで処理します。
match divide(10.0, 2.0) {
Ok(result) => println!("結果: {}", result),
Err(e) => println!("エラー: {}", e),
}
// 失敗ケースをmatchで処理します。
match divide(10.0, 0.0) {
Ok(result) => println!("結果: {}", result),
Err(e) => println!("エラー: {}", e),
}
// is_ok / is_errで状態を確認します。
let r1 = divide(10.0, 2.0);
let r2 = divide(10.0, 0.0);
println!("r1 is_ok: {}", r1.is_ok()); // true
println!("r2 is_err: {}", r2.is_err()); // true
// 標準ライブラリのパース失敗もResultで返ります。
let parsed: Result<i32, _> = "42".parse();
println!("{:?}", parsed); // Ok(42)
let failed: Result<i32, _> = "abc".parse();
println!("{:?}", failed); // Err(...)
}
コンパイルして実行すると次のように出力されます。
rustc result_ok_err.rs
./result_ok_err
結果: 5
エラー: 0で割ることはできません。
r1 is_ok: true
r2 is_err: true
Ok(42)
Err(ParseIntError { kind: InvalidDigit })
よくあるミス
よくあるミス1: Resultを無視してコンパイラ警告
Result型の戻り値を使わずに捨てると、コンパイラが警告を出します。エラーが無視されるため本番環境では問題になります。
result_mistake1.rs
use std::fs;
fn main() {
// Resultを無視すると警告が出ます。
fs::remove_file("nonexistent.txt");
println!("完了");
}
rustc result_mistake1.rs warning: unused `Result` that must be used --> result_mistake1.rs:5:5
明示的にResultを処理するか、不要な場合は let _ = で無視を明示します。
result_fix1.rs
use std::fs;
fn main() {
// matchで処理します。
match fs::remove_file("nonexistent.txt") {
Ok(_) => println!("削除しました"),
Err(e) => println!("削除失敗: {}", e),
}
// 意図的に無視する場合はlet _ =を使います。
let _ = fs::remove_file("already_gone.txt");
}
rustc result_fix1.rs ./result_fix1 削除失敗: No such file or directory (os error 2)
よくあるミス2: unwrap()の乱用でpanic
unwrap()はErrのときにpanicします。エラーが起こりえない確信がある場合以外は、matchやif let、?演算子で適切に処理します。
result_mistake2.rs
fn main() {
// ユーザー入力をunwrap()でパースするのはリスクがあります。
let input = "abc"; // 数値でない文字列
let n: i32 = input.parse().unwrap(); // パニック!
println!("{}", n);
}
rustc result_mistake2.rs
./result_mistake2
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: ParseIntError { kind: InvalidDigit }'
unwrap_or()、unwrap_or_else()、またはmatchでエラーを処理します。
result_fix2.rs
fn parse_score(s: &str) -> i32 {
s.parse::<i32>().unwrap_or_else(|e| {
println!("パース失敗 '{}': {} → 0を使います", s, e);
0
})
}
fn main() {
println!("{}", parse_score("95")); // 95
println!("{}", parse_score("abc")); // パース失敗 → 0
println!("{}", parse_score("-5")); // -5
}
rustc result_fix2.rs ./result_fix2 95 パース失敗 'abc': invalid digit found in string → 0を使います 0 -5
概要
『Result<T, E>』の型パラメータ『T』は成功値の型、『E』はエラー値の型です。エラー型には標準ライブラリの『std::io::Error』やカスタム型など、任意の型を使えます。『Result』を返す関数の戻り値を使わずに捨てるとコンパイラが警告を出します。エラーは必ず処理するか、明示的に無視する必要があります。
Rustの標準ライブラリのファイル操作・ネットワーク・パース処理など、失敗しうるほぼすべての操作が『Result』を返します。他の言語の try-catch に相当しますが、Rustでは例外が存在しないため、エラーを戻り値として明示的に扱うことがRustらしい設計です。
記事の間違いや著作権の侵害等ございましたらお手数ですがこちらまでご連絡頂ければ幸いです。