Rust dojo第56回を開催した

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

kdnakt.hatenablog.com

 

 

[第56回の様子]

2022/11/2に第56回を開催した。

 

内容としてはRust By Example 日本語版の18. エラーハンドリングの「18.1. panic」〜「18.2.1. ?によるOptionのアンパック」に取り組んだ。

 

参加者は自分を入れて6人。たくさん集まってくれて嬉しい。

 

[学んだこと]

  • 18. エラーハンドリング
  • エラーハンドリングの方法としてRustには主に3種類ある
    • panic!マクロ:テストやプロトタイプが主
    • Option型:値があるとは限らない場合
    • Result型:呼び出し元に処理させる
  • 参考として以下の記事が紹介されていた

qiita.com

  • 18.1. panic
  • panic!マクロはエラーメッセージを出力し、スタックを巻き戻し、プログラムを終了する
    • ただし、「多くの場合」プログラムを終了する、と書かれていた
    • 終了しない場合もありそうだけどよくわからなかった。ドキュメントを読むと、「メインスレッドでは〜」と記述があったので、メインスレッド以外でpanicするとプログラムを終了しないのかもしれない

doc.rust-lang.org

  • サンプルコードはこう
fn drink(beverage: &str) {
    // 甘すぎる飲み物を飲むべきではありません。
    if beverage == "lemonade" { panic!("AAAaaaaa!!!!"); }

    println!("Some refreshing {} is all I need.", beverage);
}

fn main() {
    drink("water");
    drink("lemonade");
}

// 出力は以下の通り
   Compiling playground v0.0.1 (/playground)
    Finished dev [unoptimized + debuginfo] target(s) in 0.86s
     Running `target/debug/playground`
thread 'main' panicked at 'AAAaaaaa!!!!', src/main.rs:5:33
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Some refreshing water is all I need.
  • 出力を見ると、printlnマクロの出力よりも先にpanicマクロのメッセージが出力されている。
    • 標準入力と標準エラーのフラッシュのタイミングが異なるため、ということで標準入力が先にフラッシュされるように、drink("water");をたくさん呼び出してみたがうまくいかなかった
    • dojoの後に10万回呼び出してみたが結果は変わらず:何か他に条件があるのかもしれない
  • 18.2. Option と unwrap
  • Option型は値の有無によって、Some(T)またはNoneとして扱うことができる
fn give_adult(drink: Option<&str>) {
    match drink {
        Some("lemonade") => println!("Yuck! Too sugary."),
        Some(inner)   => println!("{}? How nice.", inner),
        None          => println!("No drink? Oh well."),
    }
}

fn main() {
    let water  = Some("water");
    let lemonade = Some("lemonade");
    let void  = None;

    give_adult(water); // water? How nice.
    give_adult(lemonade); // Yuck! Too sugary.
    give_adult(void); // No drink? Oh well.
}
  • match以外にも、Optionにunwrap()を使うと値を取り出すことができる
    • ただし、Noneだった場合はpanicする
fn drink(drink: Option<&str>) {
    let inside = drink.unwrap();
    if inside == "lemonade" { panic!("AAAaaaaa!!!!"); }

    println!("I love {}s!!!!!", inside);
}

fn main() {
    let coffee = Some("coffee");
    let nothing = None;

    drink(coffee); // I love coffees!!!!!
    drink(nothing); // thread 'main' panicked at 'called `Option::unwrap()` on a `None` value', src/main.rs:21:24

}
  • unwrap()には類似のメソッドとしてunwrap_or(T)unwrap_or_else(FnOnce)がある

doc.rust-lang.org

  • ドキュメントによると、どちらもデフォルト値を渡すことができるが、unwrap_or(T)が事前評価なのに対して、unwrap_or_else(FnOnce)は遅延評価で実際に値がNoneであった時に評価される
  • 実際に試してみると以下のようになった
fn drink_or(drink: Option<&str>) {
    let inside = drink.unwrap_or(drink_tea());
    println!("I love {}s!!!!!", inside);
}

fn drink_or_else(drink: Option<&str>) {
    let inside = drink.unwrap_or_else(|| drink_tea());
    println!("I love {}s!!!!!", inside);
}

fn drink_tea() -> &'static str {
    println!("tea!!!!");
    "tea"
}

fn main() {
    let coffee = Some("coffee");
    let void = None;
    drink_or(coffee);
    // tea!!!!
    // I love coffees!!!!!

    drink_or(void);
    // tea!!!!
    // I love teas!!!!!

    drink_or_else(coffee);
    // I love coffees!!!!!
    // drink_tea()が実行されないのでtea!!!が出力されない

    drink_or_else(void);
    // tea!!!!
    // I love teas!!!!!
}
  • 18.2.1. ?によるOptionのアンパック
  • 先の例のようにmatchで値を取り出すこともできるが、?を使うとネストしたOption型を扱いやすくなる
  • 次のような構造体を考える
struct Person {
    job: Option<Job>,
}

#[derive(Clone, Copy)]
struct Job {
    phone_number: Option<PhoneNumber>,
}

#[derive(Clone, Copy)]
struct PhoneNumber {
    area_code: Option<u8>,
    number: u32,
}
  • ここでPerson型からarea_codeを取り出す関数を実装すると次のようになる
impl Person {
    fn work_phone_area_code(&self) -> Option<u8> {
        self.job?.phone_number?.area_code
    }
}

fn main() {
    let p = Person {
        job: Some(Job {
            phone_number: Some(PhoneNumber {
                area_code: Some(41),
                number: 439222222,
            }),
        }),
    };
    println!("{:?}", p.work_phone_area_code()); // Some(41)
}
  • これをmatchを使って書き直すと次のようになる。どちらがスマートかは一目瞭然...
impl Person {
    fn work_phone_area_code(&self) -> Option<u8> {
        match self.job {
            Some(job) => match job.phone_number {
                Some(phone_number) => match phone_number.area_code {
                    Some(area_code) => phone_number.area_code,
                    None => None,
                },
                None => None,
            },
            None => None,
        }
    }
}

 

[まとめ]

モブプログラミングスタイルでRust dojoを開催した。

matchを使わずに?を使う方法は以前教えてもらったのでなんとなく覚えていた。ちゃんと使いこなせるようになりたい...。

 

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

github.com