⚖️

Rust の Result 型って、やはり良いよね

2023/07/31に公開

TypeScript で実装していて、あぁ Result 型を使いたいなって...😗

Result 型ってなに

Rust における Result

https://doc.rust-jp.rs/rust-by-example-ja/error/result.html

Resultは、リッチなバージョンのOption型で, 値の不在の可能性の代わりにエラーの可能性を示します。つまり、Result<T, E>は以下の2つの結果を持ちます。

  • Ok<T>: 要素Tが見つかった場合
  • Err<E>: 要素Eとともにエラーが見つかった場合

慣例により、Okが期待される結果であり、Errは期待されない結果です。

Result 型では、何らかの処理が「成功」したこと、あるいは「失敗」したことを表現することができます。

// 数字が10以上か検証する関数
fn validate_greater_than_or_equal_to_10(num: i32) -> Result<i32, &'static str> {
    if num >= 10 {
        Ok(num)
    } else {
        Err("数字は10未満です。")
    }
}

fn main() {
    // 判定する数字を変数に代入します
    let num1: i32 = 15;
    let num2: i32 = 5;

    // 関数を使って数字が10以上かどうかを判定し、結果を表示します
    match validate_greater_than_or_equal_to_10(num1) {
        Ok(result) => println!("{}は10以上です。", result),
        Err(error) => println!("{}", error),
    }

    match validate_greater_than_or_equal_to_10(num2) {
        Ok(result) => println!("{}は10以上です。", result),
        Err(error) => println!("{}", error),
    }
}

他の言語における Result

Kotlin や Swift といった、比較的モダンと言われる言語でも採用されています。

気になる方は各言語の Result もチェックしてみてください!

Kotlin

https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-result/

Swift

https://developer.apple.com/documentation/swift/result

Rusult 型の良さ

エラーをスローするかどうか分かる

さて、以下の TypeScript で実装した関数は Error をスローするでしょうか?

// 数字が10以上かどうかを判定する関数
function validateGreaterThanOrEqualTo10(num: number): number {
    // ...数字が10以上かどうかを判定する処理
}

実は... スローしています。

// 数字が10以上かどうかを判定する関数
function validateGreaterThanOrEqualTo10(num: number): number {
    if (num >= 10) {
        return num;
    } else {
        throw new Error("数字は10未満です。");
    }
}

いや、後出しやんか!と思う方もいるかもしれません。

そうです、後出しです。

実際問題、エラーをスローしない実装も可能です。

ここで何が言いたいかというと...

どちらでも実装できてしまう = 関数の内部処理を見ないといけない ということです。

Result 型であればエラーを返す可能性があるのか、インターフェースを見れば分かるので、内部処理を追う必要がないのです。

前述した validate_greater_than_or_equal_to_10 であれば、成功の場合は i32 が返ってくるし、エラーの場合は文字列が返ってくることが一目瞭然なのです。

エラーハンドリングの実装漏れを防ぐことができる

前述した Rust のコードを TypeScrpt で実装すると、以下のように実装できると思います。

// 数字が10以上かどうかを判定する関数
function validateGreaterThanOrEqualTo10(num: number): number {
    if (num >= 10) {
        return num;
    } else {
        throw new Error("数字は10未満です。");
    }
}

// 判定結果に応じてメッセージを表示する関数
function displayResultMessage(num: number): void {
    try {
        const result = validateGreaterThanOrEqualTo10(num);
        console.log(`${result}は10以上です。`);
    } catch (ex) {
        console.log(ex);
    }
}

// 判定する数字を変数に代入します
const num1: number = 15;
const num2: number = 5;

// 関数を使って数字が10以上かどうかを判定し、結果を表示します
displayResultMessage(num1);

displayResultMessage(num2);

ただし、以下のように try...catch を使わなくても、実行できてしまいます。

その際、エラーが発生し処理が止まります。

// 数字が10以上かどうかを判定する関数
function validateGreaterThanOrEqualTo10(num: number): number {
    if (num >= 10) {
        return num;
    } else {
        throw new Error("数字は10未満です。");
    }
}

// 判定結果に応じてメッセージを表示する関数
function displayResultMessage(num: number): void {
    const result = validateGreaterThanOrEqualTo10(num);
    console.log(`${result}は10以上です。`);
}

// 判定する数字を変数に代入します
const num1: number = 1; // 1 に変更
const num2: number = 5;

// 関数を使って数字が10以上かどうかを判定し、結果を表示します
displayResultMessage(num1);  // 1 なので、エラーが発生する

displayResultMessage(num2); // 実行されない

Rust では、match 文を使って OkErr か判定する必要があり、コンパイル時にエラーハンドリングの実装漏れを教えてくれます。

fn main() {
    // 判定する数字を変数に代入します
    let num1: i32 = 15;
    let num2: i32 = 5;

    // 関数を使って数字が10以上かどうかを判定し、結果を表示します
    // match や if で判定が必要!!
    match validate_greater_than_or_equal_to_10(num1) {
        Ok(result) => println!("{}は10以上です。", result),
        Err(error) => println!("{}", error),
    }

    match validate_greater_than_or_equal_to_10(num2) {
        Ok(result) => println!("{}は10以上です。", result),
        Err(error) => println!("{}", error),
    }
}

コンパイラ様様ですね!

まとめ

Rust で開発したいエンジニアの Rust(の Result 型)は良いぞ🥰 という記事でした!

コラボスタイル Developers

Discussion