「TAURI」と「Rust」の「Option型」と「Result型」を使いこなそう
はじめに
今回は、これまで解説が蔑ろになっていた「Option型」と「Result型」を解説します。どちらも上手くいった場合と失敗した場合の場合分けをします。この辺もRustが堅牢に動作する厳密さの1つですね。
Rustでは値なしの「NULL」があり得ないので、Option型で値がある場合とない場合を場合分けします。多くのプログラミング言語は値がないことをNULLや「nil」などで表していましたが、これはプログラムがおかしな挙動になる原因にもなっていたので、RustではOption型が採用されたのでしょう。
また、Rustでは例外処理がない代わりにResult型で例外が起きなかった場合とエラーが起きた場合を場合分けします。多くのプログラミング言語は例外処理に「try」文を使うことが多かったのですが、RustではOption型の書式と一部を共通化したのではと思われます。
RustのOption型とResult型
Rustプロジェクトの作成
まず、TAURIではなくRustのみでプロジェクトを作成します。追加で必要なクレートはありません。適当なフォルダをカレントディレクトリにして、次のコマンドを実行して「option_result」などという名前でプロジェクトを新規作成します。それから作成した「option_result」フォルダをカレントディレクトリにします。
・Rustプロジェクトの作成コマンド$ cargo new option_result
Option型
Option型は値が正しい場合と間違っている場合によって場合分けしますが、Option型の変数のままでは実際に値を使うことができません。Option型の変数を「match」文で場合分けし、値が正しい場合は「Some」、間違っている場合は「None」になります。「Option型のままでは使えない」と言いましたが、正しい場合は「Some(引数) => { 引数を取り出す。 }」というような使い方をします。
関数やメソッドでのOption型の戻り値の書式は「fn 関数名 -> Option<u64>」で、戻り値のOption型のu64型が値が存在する場合のラップした値の型です。
・Option型の場合分け
次のサンプルコードを「$ cargo run」コマンドで実行したら「positive」関数の引数に10を渡し、正の整数なので「u64」型にキャストしてSome(10)を返します。match文で「p」変数から「Some」のラップを取り除いて10を取得し、"正の整数10です。"をターミナルに表示します。
ここで「let p = positive(-10);」とすると負の整数の引数なので「None」が返り、match文で"負の整数です。"をターミナルに表示します。
・「src」→「main.rs」ファイルのOption型のサンプルコード//メイン関数 fn main() { let p = positive(10); match p { Some(num) => { println!("正の整数{}です。",num); } None => { println!("負の整数です。"); } } } //正の整数か調べる関数 fn positive(i:i64) -> Option<u64> { if i >= 0 { Some(i as u64) } else { None } }
【サンプルコードの解説】
main関数でpositive関数の引数に10を渡しOption型をp変数に代入します。
match文でp変数が存在する場合Someからnumの値を取り出してターミナルに値を表示し、存在しない場合(None)、"負の整数です。"をターミナルに表示します。
positive関数で「i」引数が正の整数の場合i64型のi引数をu64型にキャストしてSomeにラップして返し、負の整数の場合はNoneをラップして返します。
・Option型の場合分けを簡略化
Option型は100%近く値が存在する場合に、毎回match文を使うのは面倒だと思われるでしょう。次のサンプルコードのようにmatch文を使わず「unwrap」メソッドで値が存在した場合の値をすぐに取り出すこともできます。電子レンジで温めた食べ物はラップを外さないと中身が食べられないように、unwrapも文字通りラップを外さないと中身の値が取り出せません。
ただし、unwrapは値が存在しない場合にパニックが起きてプログラムが終了してしまいます。100%近く値が存在する場合だけunwrapを使った方が良いでしょう。
・「src」→「main.rs」ファイルの簡略化したOption型のサンプルコード//メイン関数 fn main() { let p = positive(10).unwrap(); println!("正の整数{}です。",p); } //正の整数か調べる関数 fn positive(i:i64) -> Option<u64> { if i >= 0 { Some(i as u64) } else { None } }
【サンプルコードの解説】
main関数でpositive関数の引数に10を渡しており必ず値が存在するためunwrapします。
"正の整数10です。"をターミナルに表示します。
ここで「let p = positive(-10).unwrap();」とすると負の整数の引数なのでNoneが返りパニックになるため、代わりにデフォルト値を使えるようにする「unwrap_or」メソッドを使います。
・「src」→「main.rs」ファイルのパニックを起こさないように簡略化したOption型のサンプルコード//メイン関数 fn main() { let p = positive(-10).unwrap_or(10); println!("正の整数にした場合{}です。",p); } //正の整数か調べる関数 fn positive(i:i64) -> Option<u64> { if i >= 0 { Some(i as u64) } else { None } }
【サンプルコードの解説】
main関数でpositive関数の引数に-10を渡しており値が存在しないため、unwrap_orでデフォルト値10を取得します。
"正の整数にした場合10です。"をターミナルに表示します。
Result型
Result型は値が正しい場合と例外の場合によって場合分けしますが、Result型の変数のままでは実際に値を使うことができません。Result型の変数を「match」文で場合分けし、値が正しい場合は「Ok」、例外の場合は「Err」になります。
「Result型のままでは使えない」と言いましたが、正しい場合は「Ok(引数) => { 引数を取り出す。 }」、エラーの場合は「Err(引数) => { エラー内容などの引数を取り出す。 }」というような使い方をします。
Option型と似ていますが、Result型はエラーの場合も値を引数に渡せます。最初にも言ったように値にNULLがあり得る場合をOption型で実装し、例外処理の場合をResult型で実装します。関数やメソッドでのResult型の戻り値の書式は「fn 関数名 -> Result<u64, String>」で、これは戻り値のResult型のu64型が値が正しい場合のラップした値の型、Stringがエラーの場合の値の型です。ただしエラーの場合の型はエラー用の型が用意されていることが多く、例えばファイル入出力の場合「std::io::Error」などがあります。
・Result型の場合分け
次のサンプルコードを実行したら、positive関数の引数に正の整数10が渡されており値が正しいためOkを返し、match文でResult型のOkからラップを外したnum引数を取得して"正の整数10です。"をターミナルに表示します。
ここで「let p = positive(-10);」とすると負の整数の引数なのでErrが返り、match文で「err」引数をErrからラップを外して"負の整数です。"をターミナルに表示します。
・「src」→「main.rs」ファイルのResult型のサンプルコード//メイン関数 fn main() { let p = positive(10); match p { Ok(num) => { println!("正の整数{}です。",num); } Err(err) => { println!("{}",err); } } } //正の整数か調べる関数 fn positive(i:i64) -> Result<u64, String> { if i >= 0 { Ok(i as u64) } else { Err(String::from("負の整数です。")) } }
【サンプルコードの解説】
main関数でpositive関数の引数に10を渡しResult型をp変数に代入します。
match文でp変数が正しい場合はOkからnumの値を取り出してターミナルに値を表示し、例外の場合はErrからerrの値を取り出して"負の整数です。"をターミナルに表示します。
positive関数でiが正の整数の場合はi64型の「i」引数をu64型にキャストしてOkにラップして返し、負の整数の場合はErrにエラー内容をラップして返します。
・Result型の場合分けの簡略化
例外が起きる可能性がとても低いのに、毎回match文で場合分けするのは面倒に思われるでしょう。その場合は即座にunwrapメソッドでResult型のOkの中身を取り出すことができます。Result型もOption型と同様、unwrapでラップを外して中身の食べ物を使えます。
ただしOption型同様、例外が起きた場合にパニックを起こします。パニックを起こすとプログラムが終了してしまうので、例外が起きない場合だけunwrapを使いましょう。
・「src」→「main.rs」ファイルの簡略化したResult型のサンプルコード//メイン関数 fn main() { let p = positive(10).unwrap(); println!("正の整数{}です。",p); } //正の整数か調べる関数 fn positive(i:i64) -> Result<u64, String> { if i >= 0 { Ok(i as u64) } else { Err(String::from("負の整数です。")) } }
【サンプルコードの解説】
main関数でpositive関数の引数に10を渡しており、必ず値が正しいためunwrapします。
"正の整数10です。"をターミナルに表示します。
ここで「let p = positive(-10).unwrap();」としたら負の整数の引数なのでパニックを起こします。それを回避するために「unwrap_or」メソッドを使って例外が起きてErrを返した場合、代わりにunwrap_orの引数をデフォルト値として返します。
・「src」→「main.rs」ファイルのパニックを起こさないように簡略化したResult型のサンプルコード//メイン関数 fn main() { let p = positive(-10).unwrap_or(10); println!("正の整数にした場合{}です。",p); } //正の整数か調べる関数 fn positive(i:i64) -> Result<u64, String> { if i >= 0 { Ok(i as u64) } else { Err(String::from("負の整数です。")) } }
【サンプルコードの解説】
main関数でpositive関数の引数に-10を渡しておりエラーになるためunwrap_orでデフォルト値10を取得します。
"正の整数にした場合10です。"をターミナルに表示します。