3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Result

Nullableな値を扱う際にOption型があるというのは前記事で取り上げましたが、より具体的に「処理の成否」を格納するための型としてResult型というものがあります。
Result型は内部にOk<T>Err<E>TEはどちらもジェネリクス)の2つの要素を持ちます。処理が成功したときの値はOk<T>に、失敗したときのエラーはErr<E>に格納されます。

処理が失敗する可能性がある処理はResult型を返すのが一般的です。ResultOption同様にmatchによるパターンマッチングが利用できます。

fn divide(a: i32, b: i32) -> Result<i32, String> {
    if b == 0 {
        Err("Division by zero".to_string())
    } else {
        Ok(a / b)
    }
}

fn main() {
    let result = divide(10, 2);
    match result {
        Ok(value) => println!("Result: {}", value),
        Err(error) => println!("Error: {}", error),
    }

    let result = divide(10, 0);
    match result {
        Ok(value) => println!("Result: {}", value),
        Err(error) => println!("Error: {}", error),
    }
}

Okに値が入っているときにのみ特定の処理を行いたい場合、and_thenメソッドが使えます。matchを使うと、Errの場合のパターンも記述しなくてはならない上に、分岐が連続するとブロックがネストされて見にくくなります。このメソッドを使うことで、記述量を減らせます。

fn parse_to_number(input: &str) -> Result<i32, String> {
    input.parse::<i32>().map_err(|_| "Failed to parse number".to_string())
}

fn divide_by_two(num: i32) -> Result<i32, String> {
    if num % 2 == 0 {
        Ok(num / 2)
    } else {
        Err("Cannot divide an odd number by 2".to_string())
    }
}

fn main() {
    let result = Ok("42")
        .and_then(parse_to_number) // "42" をパースして 42 を返す
        .and_then(divide_by_two);  // 42 を 2 で割り、21 を返す

    match result {
        Ok(value) => println!("Final result: {}", value),
        Err(error) => println!("Error: {}", error),
    }
}

また、Okに入った値をもとに新たな値を返したい場合はmapメソッドが使えます。

fn main() {
    let result = Ok(10)
        .map(|x| x * 2)  // 成功値 10 を 20 に加工
        .map(|x| x + 5); // 20 に 5 を足して 25 に加工

    match result {
        Ok(value) => println!("Processed value: {}", value), // Processed value: 25
        Err(error) => println!("Error: {}", error),
    }
}

例外のthrowではなくResult型を返却する、というのは(筆者のにわか知識ですが)関数型プログラミングを想像させます。このタイプのプログラミングはしたことがないので非常に興味があります。エラーハンドリングを型システムで定義するというのは安全そうですね。TypeScriptにもneverthrowなどのライブラリがあるので、いつか触ってみたいです。

and_thenmap

  • Okが存在するときの処理を書ける
  • 値を返すことができる

という共通点があります。どう使い分けるのかはいろいろアイデアがありそうですが、個人的にはこちらの記事が非常に参考になりました。

筆者の理解では、

  • 複数の処理をチェーンしたい、途中で失敗する可能性がある場合→and_then
  • 単純に値の変換だけしたい、失敗ケースは考えない場合→map

と使い分けるのが良さそうに思えました。

Result型に名前をつける

Resultは一種の型なので、よく使うであろうResult型をtypeキーワードで宣言しておくと便利です。

type MyResult<T> = Result<T, String>;

fn divide(a: i32, b: i32) -> MyResult<i32> {
    if b == 0 {
        Err("Division by zero".to_string())
    } else {
        Ok(a / b)
    }
}

fn main() {
    let result: MyResult<i32> = divide(10, 2);
    println!("{:?}", result);
}

Early Return

いくつかの処理を連続して実行し、それぞれの処理の成否によっては処理を中断しErrを返すパターンをEarly Return(早期リターン)と呼びます。
match式が値を返せることを利用し、Okであればその値を変数にバインディングして次に進み、ErrであればErrreturnするパターンです。

この方法であればネストは発生せず、かつ「処理Aが失敗したら全体的に失敗とするが、処理Bが失敗したらフォールバック値を使う」といった柔軟な処理が実装できます(Resultというより、Optionの特徴に着目しているといえます)。

fn divide(a: i32, b: i32) -> Result<i32, String> {
    if b == 0 {
        Err("Division by zero".to_string())
    } else {
        Ok(a / b)
    }
}

fn increment_if_even(x: i32) -> Result<i32, String> {
    if x % 2 == 0 {
        Ok(x + 1)
    } else {
        Err("Not an even number".to_string())
    }
}

fn process(value: i32) -> Result<i32, String> {
    let value = match divide(value, 2) {
        Ok(v) => v,
        Err(e) => return Err(e),
    };

    let value = match increment_if_even(value) {
        Ok(v) => v,
        Err(e) => return Err(e),
    };

    Ok(value)
}

fn main() {
    match process(8) {
        Ok(result) => println!("Success: {}", result),
        Err(error) => println!("Error: {}", error),
    }
}

?を使って安全にOkの値を取り出す

「ある処理の値がErrであればそれをreturnする」という前提のもと、より簡潔に書けるのが?構文です。Result型を返すメソッドの後に?をつけると、Errが返されたときのreturnが不要になります(自動で行ってくれる)。

fn divide(a: i32, b: i32) -> Result<i32, String> {
    if b == 0 {
        Err("Division by zero".to_string())
    } else {
        Ok(a / b)
    }
}

fn add_five(x: i32) -> Result<i32, String> {
    Ok(x + 5)
}

fn calculate(a: i32, b: i32) -> Result<i32, String> {
    let result = divide(a, b)?; // Okなら値を取得、Errならreturn
    add_five(result)
}

fn main() {
    match calculate(10, 2) {
        Ok(value) => println!("Final result: {}", value),
        Err(error) => println!("Error: {}", error),
    }
}
3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?