第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を開催した。
例外処理はどの言語も大変だなあ...。
今週のプルリクエストはこちら。