Rust dojo第57回を開催した

第57回です。前回はこちら。

kdnakt.hatenablog.com

 

 

[第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型にあまり慣れていないのでどこかで実戦投入してみたい...。

 

今週のプルリクエストはこちら。

github.com