【Rust】Resultの基本と便利なメソッドの使い方

Optionに続きResultの紹介です。

Optionと同様にRustコーディングの肝といえる存在なので大事ですね。

前回同様、自身の勉強のアウトプット・整理と同時に、同じようにRustを勉強している方や「公式ドキュメントはちょっと、、」というような方が気楽に見れるようにサンプルコードをメインに簡単にまとめてみました。

この記事でできること
  • Result型の基本
  • Result型の便利なメソッドとその使い方
目次

基本

Result<T,E>は次のいずれかの値を示す型です。

  • Ok(T):成功もしくは正常な状態
  • Err(E):失敗もしくは異常な状態

Result<T,E>自体の実装は下記のようにとてもシンプルです。

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

Optionもそうでしたが、ただの列挙型(Enum)です。
正常な状態を示したい場合はOk(T)、エラーを示したい場合はErr(E)というようになります。

使い方は以下のようになります。

fn sample() {
    let ok: Result<i32, &str> = Ok(200);
    println!("{:?}", ok);
    // -> Ok(200)

    let error: Result<i32, &str> = Err("failed");
    println!("{:?}", error);
    // -> Err("failed")
}

便利なメソッド

公式ドキュメントからResultで特に使いそうなメソッドをピックアップしてみました。

サンプルコードとともに注意点も簡単に記載しています。

参考リンク

unwrap

Ok(T)の中身を取得する。Errの場合はpanicを引き起こす。

所有権の移動が発生するので同じものに対して複数回は実行できない。

fn sample() {
    let ok: Result<i32, String> = Ok(200);
    println!("{:?}", ok.unwrap()); // -> 200
    println!("{:?}", ok.unwrap()); // 上で所有権が移動済なのでコンパイルエラーとなる

    let error: Result<i32, String> = Err("failed".to_string());
    println!("{:?}", error.unwrap()); // Errの場合はpanicとなる
}

公式ドキュメントでは基本的に使用は推奨されておらず、Errケースを処理できるような方法が望ましいとされている。(unwrap_orなどを使うなど)

unwrap_or

unwrapの派生形。

Errに対して実行した場合は引数に指定した値が返却される。

fn sample() {
    let ok: Result<i32, String> = Ok(200);
    println!("{:?}", ok.unwrap_or(-1)); 
    // -> 200
    
    println!("{:?}", ok.unwrap_or(-1)); 
    // 上で所有権が移動済なのでコンパイルエラーとなる

    let error: Result<i32, String> = Err("failed".to_string());
    println!("{:?}", error.unwrap_or(-1)); 
    // -> -1
}

unwrap_or_else

unwrap_orと同様だが、引数には関数を渡す。

Errの場合に引数に渡した関数が実行されその結果が返却される。

fn sample() {
    let ok: Result<i32, String> = Ok(200);
    println!("{:?}", ok.unwrap_or_else(|str| -1));
    // -> 200

    println!("{:?}", ok.unwrap_or_else(|str| -1));
    // 上で所有権が移動済なのでコンパイルエラーとなる

    let error: Result<i32, String> = Err("failed".to_string());
    println!("{:?}", error.unwrap_or_else(|str| -1));
    // -> -1
}

unwrap_or_default

unwrap_orと同様だが、引数指定なしで使用可能。

Errの場合にResult<T,E>におけるT型のデフォルト値を返すことができる。

fn sample() {
    let ok: Result<i32, String> = Ok(200);
    println!("{:?}", ok.unwrap_or_default());
    // -> 200

    println!("{:?}", ok.unwrap_or_default());
    // 上で所有権が移動済なのでコンパイルエラーとなる

    let error: Result<i32, String> = Err("failed".to_string());
    println!("{:?}", error.unwrap_or_default());
    // -> 0
}

expect

Ok(T)の中身を取得する。Errの場合は引数に指定した文字列でpanicを発生。

所有権の移動が発生するので同じ物に対して複数回は行えない。

fn sample() {
    let ok: Result<i32, String> = Ok(200);
    println!("{:?}", ok.expect("no error"));
    // -> 200

    println!("{:?}", ok.expect("compile error"));
    // 上で所有権が移動済なのでコンパイルエラーとなる

    let error: Result<i32, String> = Err("failed".to_string());
    println!("{:?}", error.expect("panic!!"));
    // -> Errの場合はpanicとなる
}

as_ref

Result<T,E>Result<&T,&E>にして参照する。

unwrapmapなどのメソッドはResult<T,E>の中身を消費(所有権を移動)するメソッドだが、as_refを使用して参照を挟むことで消費しないようにすることができる。

fn sample() {
    let ok: Result<i32, String> = Ok(200);
    println!("{:?}", ok.unwrap()); // unwrapで所有権が移動する
    println!("{:?}", ok.unwrap()); // 上の行で所有権が移動済なのでコンパイルエラーとなる

    let ok2: Result<i32, String> = Ok(200);
    println!("{:?}", ok2.as_ref().unwrap()); // as_refをはさむことで所有権の移動が発生しない
    println!("{:?}", ok2.unwrap()); // この時点ではまだ所有権は移動していないのでコンパイルエラーとならない
    println!("{:?}", ok2.as_ref().unwrap()); // 上の行で所有権が移動済なので参照も作れない(コンパイルエラー)
}

map

Result<T,E>Result<U,E>にする。

引数に渡した関数で変換・整形処理を行い、その結果としてResult<U,E>を返却する。

Errに対して実行した場合、引数に渡した関数は実行されないことに注意。

fn sample() {
    let ok: Result<i32, String> = Ok(200);
    println!("{:?}", ok.map(|s| s * 2));
    // -> Ok(400)

    let error: Result<i32, String> = Err("failed".to_string());
    println!("{:?}", error.map(|s| s * 2));
    // -> Err("failed") ※mapに渡した関数は実行されない
}

map_or

mapにデフォルト値がついたもの。

戻り値がResult型ではなく、中身のT型となることに注意。

mapと同様に、Errの場合は引数に渡した関数は実行されないことに注意。

fn sample() {
    let ok: Result<i32, String> = Ok(200);
    println!("{:?}", ok.map_or(999, |s| s * 2));
    // -> 400
    
    let error: Result<i32, String> = Err("failed".to_string());
    println!("{:?}", error.map_or(999, |s| s * 2));
    // -> 999 ※mapに渡した関数は実行されない
}

map_or_else

map_orのデフォルト値指定部分が関数になったもの。

fn sample() {
    let ok: Result<usize, String> = Ok(200);
    println!("{:?}", ok.map_or_else(|error| error.len(), |s| s * 2));
    // -> 400

    let error: Result<usize, String> = Err("failed".to_string());
    println!("{:?}", error.map_or_else(|error| error.len(), |s| s * 2));
    // -> 6 ※mapに渡した関数は実行されない
}

ok

Result<T,E>Option<T>に変換する。

Ok(T)の場合はSome(T)に、Err(E)の場合はNoneにそれぞれ変換される。

fn sample() {
    let ok: Result<usize, String> = Ok(200);
    println!("{:?}", ok.ok());
    // -> Some(200)

    let error: Result<usize, String> = Err("failed".to_string());
    println!("{:?}", error.ok());
    // -> None
}

err

Result<T,E>Option<T>に変換する。

Ok(T)の場合はNoneに、Err(E)の場合はSome(E)にそれぞれ変換される。

fn sample() {
    let ok: Result<usize, String> = Ok(200);
    println!("{:?}", ok.err());
    // -> None

    let error: Result<usize, String> = Err("failed".to_string());
    println!("{:?}", error.err());
    // -> Some("failed")
}

and / or

and,orという名前ですが、真理値表のように考えると混乱するかも。

ドキュメントにあるように以下のように動作することに注意。

andOk(T)の場合は引数に渡した値を、Err(E)の場合はErr(E)自身を返す
orOk(T)の場合はOk(T)自身を、Err(E)の場合は引数に渡した値を返す
fn sample() {
    let ok: Result<i32, &str> = Ok(200);
    let another_ok: Result<i32, &str> = Ok(999);
    let error: Result<i32, &str> = Err("failed");
    let another_error: Result<i32, &str> = Err("another failed");

    // and
    println!("{:?}", ok.and(another_ok));       // -> Ok(999)
    println!("{:?}", ok.and(error));            // -> Err("failed")
    println!("{:?}", error.and(ok));            // -> Err("failed")
    println!("{:?}", error.and(another_error)); // -> Err("failed")

    // or
    println!("{:?}", ok.or(another_ok));       // -> Ok(200)
    println!("{:?}", ok.or(error));            // -> Ok(200)
    println!("{:?}", error.or(ok));            // -> Ok(200)
    println!("{:?}", error.or(another_error)); // -> Err("another failed")
}

and_then / or_else

andorの引数が関数になったもの。

fn sample() {
    let ok: Result<i32, &str> = Ok(200);
    let another_ok: Result<i32, &str> = Ok(999);
    let error: Result<i32, &str> = Err("failed");
    let another_error: Result<i32, &str> = Err("another failed");

    // and
    println!("{:?}", ok.and_then(|i| another_ok));       // -> Ok(200)
    println!("{:?}", ok.and_then(|i| error));            // -> Err("failed")
    println!("{:?}", error.and_then(|i| ok));            // -> Err("failed")
    println!("{:?}", error.and_then(|i| another_error)); // -> Err("failed")

    // or
    println!("{:?}", ok.or_else(|s| another_ok));       // -> Ok(200)
    println!("{:?}", ok.or_else(|s| error));            // -> Ok(200)
    println!("{:?}", error.or_else(|s| ok));            // -> Ok(200)
    println!("{:?}", error.or_else(|s| another_error)); // -> Err("failed")
}

最後に

2つの型を内包している分、OptionよりもResultのほうが難しく感じます。

ですが、エラーを値としてコントロールできるというのは安心感があります。

Result関連のメソッドはnightly channelでさまざまな更新が進んでいるようで、今後はもっと使いやすくなることが期待されます。

より詳細を知りたい方や余力がある方はぜひ公式ドキュメントも確認してみてください。

よかったらシェアしてね!
  • URLをコピーしました!
目次