第56回です。前回はこちら。
[第56回の様子]
2022/11/2に第56回を開催した。
内容としてはRust By Example 日本語版の18. エラーハンドリングの「18.1. panic」〜「18.2.1. ?によるOptionのアンパック」に取り組んだ。
参加者は自分を入れて6人。たくさん集まってくれて嬉しい。
[学んだこと]
- 18. エラーハンドリング
- エラーハンドリングの方法としてRustには主に3種類ある
- panic!マクロ:テストやプロトタイプが主
- Option型:値があるとは限らない場合
- Result型:呼び出し元に処理させる
- 参考として以下の記事が紹介されていた
- 18.1. panic
- panic!マクロはエラーメッセージを出力し、スタックを巻き戻し、プログラムを終了する
- ただし、「多くの場合」プログラムを終了する、と書かれていた
- 終了しない場合もありそうだけどよくわからなかった。ドキュメントを読むと、「メインスレッドでは〜」と記述があったので、メインスレッド以外でpanicするとプログラムを終了しないのかもしれない
- サンプルコードはこう
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)
がある
- ドキュメントによると、どちらもデフォルト値を渡すことができるが、
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を使わずに?を使う方法は以前教えてもらったのでなんとなく覚えていた。ちゃんと使いこなせるようになりたい...。
今週のプルリクエストはこちら。