🌦️

例外代わりの `Result<T, E>`

に公開

例外代わりの Result<T, E>

Rust には Result<T, E> という型があります。だいたいこういう定義です。

enum Result<T, E> {
    Ok(T),
    Err(E),
}

Result<T, E> は定義から分かるとおり、結果 (成否) を表す型で、 Ok のときの型 TErr のときの型 E のどちらか一方を保持します。

Rust は他のプログラミング言語にあるような例外がありません。代わりにエラーハンドリングにはいくつかの方法がありますが、主に Result<T, E> 型を使用します。

Result<T, E> は標準ライブラリを含めて広く使われている型です。 Option<T> と同様に、 Rust をはじめるとすぐにふれることになる型のひとつです。

ここまで Option<T> のときとほとんど同じ書き出しにしてみました。

基本的な使い方

Result<T, E> は、たとえば次のように構築できます。

let r1: Result<i32, &str> = Err("Error!");
let r2: Result<i32, &str> = Ok(123);

TE は任意の型を取れるので、↑の例のように E として &str を取ることもできますし、 () のように実質エラーの詳細を持たないこともできます。

let r1: Result<bool, ()> = Err(());
let r2: Result<bool, ()> = Ok(true);

Error trait を実装した型を持つことが多いです。 Error についてはまた別で書きます (たぶん) 。

Result<T, E> は、たとえば次のように分解できます。

let r: Result<i32, &str> = Ok(123);
match r {
    Ok(t) => println!("{t}"),
    Err(e) => println!("{e}"),
}

match「 if より match 」 でも触れています。

Result<T, E>bool にするメソッド

is_err / is_err_and / is_ok / is_ok_and

Option<T>is_noneis_some などと同様に is_erris_ok などで bool に変換できます。

let r: Result<i32, &str> = Err("Error!");
assert!(r.is_err());
let r: Result<i32, &str> = Ok(123);
assert!(r.is_ok());

let r: Result<i32, &str> = Err("Error!");
assert!(r.is_err_and(|e| e == "Error!"));
let r: Result<i32, &str> = Ok(123);
assert!(r.is_ok_and(|t| t == 123));

Result<T, E>TE にする (中身を取り出す) メソッド

expect / unwrap

Option<T>expectunwrap などと同様に Result<T, E>expectunwrap でも中身を取り出せます。

let r: Result<i32, &str> = Ok(123);
assert_eq!(r.unwrap(), 123);

let r: Result<i32, &str> = Err("Error!");
r.unwrap(); // panic
// called `Result::unwrap()` on an `Err` value: "Error!"

let r: Result<i32, &str> = Ok(123);
assert_eq!(r.expect("r is Ok"), 123);

let r: Result<i32, &str> = Err("Error!");
r.expect("r is Ok"); // panic
// r is Ok: "Error!"

expect_err / unwrap_err

Option<T>T のみなので、これで終わりですが、 Result<T, E>OkErr の 2 つがあるので、そのための unwrap もあります。

let r: Result<i32, &str> = Ok(123);
r.unwrap_err(); // panic
// called `Result::unwrap_err()` on an `Ok` value: 123

let r: Result<i32, &str> = Err("Error!");
assert_eq!(r.unwrap_err(), "Error!");

let r: Result<i32, &str> = Ok(123);
r.expect_err("r is Err"); // panic
// r is Err: 123

let r: Result<i32, &str> = Err("Error!");
assert_eq!(r.expect_err("r is Err"), "Error!");

unwrap_or / unwrap_or_default / unwrap_or_else

unwrap_orErr の場合の値を指定することで unwrap します。 unwrap_or_defaultErr の場合の値として TDefault::default の値を使用します。 unwrap_or_elseErr の場合の値の代わりに値を返す関数を指定します。

let r: Result<i32, &str> = Ok(123);
assert_eq!(r.unwrap_or(456), 123);
let r: Result<i32, &str> = Err("Error!");
assert_eq!(r.unwrap_or(456), 456);

let r: Result<i32, &str> = Ok(123);
assert_eq!(r.unwrap_or_default(), 123);
let r: Result<i32, &str> = Err("Error!");
assert_eq!(r.unwrap_or_default(), 0);

let r: Result<i32, &str> = Ok(123);
assert_eq!(r.unwrap_or_else(|_| 456), 123);
let r: Result<i32, &str> = Err("Error!");
assert_eq!(r.unwrap_or_else(|_| 456), 456);

ドクターメイト株式会社での運用

Option<T> のときにも触れたのですが、ドクターメイト株式会社では unwrap を使用しないようにしています。テストコードでも、です。理由は、テストコードで unwrap を使用していると、プロダクションコード側で誤って unwrap の使用を残してしまっていても検索でそれに気づくことが難しくなるためです。安全に unwrap できる場合は代わりに expect を使用しています。

一方で unwrap_err は意外とテストにおけるエラーの検証に使われています。 unwrap はダメだけど、 unwrap_err はいいんですね。ふしぎですね。

次回予告

ほとんど Option<T> と同じですね! 次回もほとんど Option<T> と同じになりそうですね!! ? 演算子に触れると思います。たぶん。

参考

GitHubで編集を提案
ドクターメイト

Discussion