第57回です。前回はこちら。
[第57回の様子]
2022/11/9に第57回を開催した。
内容としてはRust By Example 日本語版の18. エラーハンドリングの「18.2.2. Combinators: map」〜「18.3. Result」に取り組んだ。
参加者は自分を入れて6人。安定して集まってくれて嬉しい。
[学んだこと]
- 18.2.2. Combinators: map
- Option型の中身を取り扱うのにmatchを使う方法を先週学習した
- しかし、matchは複数回連続して呼び出す場合は面倒である
#[derive(Debug)] enum Food { Apple, Carrot, Potato } #[derive(Debug)] struct Peeled(Food); #[derive(Debug)] struct Chopped(Food); #[derive(Debug)] struct Cooked(Food); // matchを使って食べ物の状態を変化させる fn peel(food: Option<Food>) -> Option<Peeled> { match food { Some(food) => Some(Peeled(food)), None => None, } } fn chop(peeled: Option<Peeled>) -> Option<Chopped> { match peeled { Some(Peeled(food)) => Some(Chopped(food)), None => None, } } // mapを使うとシンプルに書ける fn cook(chopped: Option<Chopped>) -> Option<Cooked> { chopped.map(|Chopped(food)| Cooked(food)) } fn eat(food: Option<Cooked>) { match food { Some(food) => println!("Mmm. I love {:?}", food), None => println!("Oh no! It wasn't edible."), } } fn main() { let apple = Some(Food::Apple); let cooked_apple = cook(chop(peel(apple))); eat(cooked_apple); // Mmm. I love Cooked(Apple) }
- mapを複数チェインさせると、もっとシンプルに書くことができる
fn process(food: Option<Food>) -> Option<Cooked> { food.map(|f| Peeled(f)) .map(|Peeled(f)| Chopped(f)) .map(|Chopped(f)| Cooked(f)) } fn main() { let potato = None; let cooked_potato = process(potato); eat(cooked_potato); // Oh no! It wasn't edible. }
- 18.2.3. Combinators: and_then
- map()関数はOption<T>型を返すので、map()に渡すクロージャがOption<T>を返す場合、最終的にOption<Option<T>>型になってしまう
- これをOption<T>型にして扱うにはand_then()を用いる
#![allow(dead_code)] #[derive(Debug)] enum Food { CordonBleu, Steak, Sushi } // 寿司の材料がない fn have_ingredients(food: Food) -> Option<Food> { match food { Food::Sushi => None, _ => Some(food), } } // コルドンブルーのレシピがない fn have_recipe(food: Food) -> Option<Food> { match food { Food::CordonBleu => None, _ => Some(food), } } // 材料とレシピがある=調理可能 fn cookable_v2(food: Food) -> Option<Food> { have_recipe(food).and_then(have_ingredients) } fn eat(food: Food) { match cookable_v2(food) { Some(food) => println!("Yay! We get to eat {:?}.", food), None => println!("Oh no. We don't get to eat."), } } fn main() { let (cordon_bleu, steak, sushi) = (Food::CordonBleu, Food::Steak, Food::Sushi); eat(cordon_bleu); eat(steak); eat(sushi); }
- 18.3. Result
- ResultはOptionのリッチなバージョンで、値の不在ではなくエラーを示す
Ok<T>
:値TがあるErr<E>
:エラーEがあるResult.unwrap()
はT型の値を返すかpanicする
fn multiply(first_number_str: &str, second_number_str: &str) -> i32 { // 文字列をi32型にパース let first_number = first_number_str.parse::<i32>().unwrap(); let second_number = second_number_str.parse::<i32>().unwrap(); first_number * second_number } fn main() { let twenty = multiply("10", "2"); println!("double is {}", twenty); // double is 20 let tt = multiply("t", "2"); // thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: ParseIntError { kind: InvalidDigit }', src/main.rs:4:56 println!("double is {}", tt); }
- Result型はmain関数のリターン型にすることもできる
use std::num::ParseIntError; fn main() -> Result<(), ParseIntError> { let number_str = "10"; let number = match number_str.parse::<i32>() { Ok(number) => number, Err(e) => return Err(e), }; println!("{}", number); Ok(()) } // 10が出力されて終了する
- i32にパースできない文字列の場合、以下のように異常終了する
use std::num::ParseIntError; fn main() -> Result<(), ParseIntError> { let number_str = "tt"; let number = match number_str.parse::<i32>() { Ok(number) => number, Err(e) => return Err(e), }; println!("{}", number); Ok(()) } // 実行すると以下のように出力される // Error: ParseIntError { kind: InvalidDigit } // コンパイルしたバイナリの終了ステータスは1(失敗)
[まとめ]
モブプログラミングスタイルでRust dojoを開催した。
他の言語でもOption型にあまり慣れていないのでどこかで実戦投入してみたい...。
今週のプルリクエストはこちら。