第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を使わずに?を使う方法は以前教えてもらったのでなんとなく覚えていた。ちゃんと使いこなせるようになりたい...。
今週のプルリクエストはこちら。