Rust の Result 型って、やはり良いよね
TypeScript で実装していて、あぁ Result 型を使いたいなって...😗
Result 型ってなに
Rust における Result
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
Swift
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
文を使って Ok
か Err
か判定する必要があり、コンパイル時にエラーハンドリングの実装漏れを教えてくれます。
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 型)は良いぞ🥰 という記事でした!
Discussion