第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型にあまり慣れていないのでどこかで実戦投入してみたい...。
今週のプルリクエストはこちら。