第58回です。前回はこちら。
[第58回の様子]
2022/11/16に第58回を開催した。
内容としてはRust By Example 日本語版の18. エラーハンドリングの「18.3.1. Resultのmap」〜「18.3.2. Resultに対するエイリアス」に取り組んだ。
参加者は自分を入れて8人。ここ1ヶ月くらい、入れ替わりで安定して6人集まっていたのがたまたまほぼ全員集まってくれた形。
[学んだこと]
- 18.3.1. Resultのmap
- 基本的にpanic!()マクロを使うのではなく、呼び出し側にエラーハンドリングをさせるのが良い
- Resultをmatchで処理すると
Err(e) => Err(e)
が以下のように連続する
use std::num::ParseIntError; fn multiply(first_number_str: &str, second_number_str: &str) -> Result<i32, ParseIntError> { match first_number_str.parse::<i32>() { Ok(first_number) => { match second_number_str.parse::<i32>() { Ok(second_number) => { Ok(first_number * second_number) }, Err(e) => Err(e), } }, Err(e) => Err(e), } }
- これを避けるにはmap()やand_then()を利用するとシンプルに書ける
use std::num::ParseIntError; fn multiply(first_number_str: &str, second_number_str: &str) -> Result<i32, ParseIntError> { first_number_str.parse::<i32>().and_then(|first_number| { second_number_str.parse::<i32>().map(|second_number| first_number * second_number) }) }
- ここでand_then()の中でmap()が呼ばれている
- 両方map()にしてしまうと、以下のコンパイルエラーになってしまうので注意が必要
error[E0308]: mismatched types --> src/main.rs:10:5 | 9 | fn multiply(first_number_str: &str, second_number_str: &str) -> Result<i32, ParseIntError> { | -------------------------- expected `Result<i32, ParseIntError>` because of return type 10 | / first_number_str.parse::<i32>().map(|first_number| { 11 | | second_number_str.parse::<i32>().map(|second_number| first_number * second_number) 12 | | }) | |______^ expected `i32`, found enum `Result` | = note: expected enum `Result<i32, _>` found enum `Result<Result<i32, ParseIntError>, _>`
- 18.3.2. Resultに対するエイリアス
- Resultについてもエイリアスを定義できる
- io::Resultのようにモジュールで定義されているものもある
// io::Error型を含むResult pub type Result<T> = Result<T, Error>;
- ParseIntErrorを例にとると次のようになる
use std::num::ParseIntError; type AliasedResult<T> = Result<T, ParseIntError>; fn multiply(first_number_str: &str, second_number_str: &str) -> AliasedResult<i32> { first_number_str.parse::<i32>().and_then(|first_number| { second_number_str.parse::<i32>().map(|second_number| first_number * second_number) }) } fn print(result: AliasedResult<i32>) { match result { Ok(n) => println!("n is {}", n), Err(e) => println!("Error: {}", e), } }
- これは例えば、i64にパースする関数があった場合、次のように流用できる
fn multiplyi64(first_number_str: &str, second_number_str: &str) -> AliasedResult<i64> { first_number_str.parse::<i64>().and_then(|first_number| { second_number_str.parse::<i64>().map(|second_number| first_number * second_number) }) } fn printi64(result: AliasedResult<i64>) { match result { Ok(n) => println!("n is {}", n), Err(e) => println!("Error: {}", e), } } fn main() { print(multiply("12345678901", "3332")); // i32を越えるので以下のエラーがプリントされる // Error: number too large to fit in target type printi64(multiplyi64("12345678901", "3332")); // i64に収まるので計算結果が表示される // n is 41135802098132 }
- ここで、f32型用のメソッドも利用しようとしたが、型が合わずうまく既存のエイリアスにまとめられなかった
error[E0308]: mismatched types --> src/main.rs:15:5 | 14 | fn multiplyf32(first_number_str: &str, second_number_str: &str) -> AliasedResult<f32> { | ------------------ expected `Result<f32, ParseIntError>` because of return type 15 | / first_number_str.parse::<f32>().and_then(|first_number| { 16 | | second_number_str.parse::<f32>().map(|second_number| first_number * second_number) 17 | | }) | |______^ expected struct `ParseIntError`, found struct `ParseFloatError` | = note: expected enum `Result<_, ParseIntError>` found enum `Result<_, ParseFloatError>`
- Javaみたいな言語だとこういう時に
catch (ParseError e)
みたいにまとめられて楽なのに、みたいな話をした - 例外を効率的に扱えるのが継承のある言語のいいところ、という指摘があってなるほどと思った。
- RustではResultもいいけど最近は色々あってanyhowが鉄板という話も聞けた。今度触ってみよう
[まとめ]
モブプログラミングスタイルでRust dojoを開催した。
例外処理はどの言語も大変だなあ...。
今週のプルリクエストはこちら。