一週空いて第53回です。前回はこちら。
[第53回の様子]
2022/09/14に第53回を開催した。
内容としてはRust By Example 日本語版の「16.9. Disambiguating overlapping traits」と「17. macro_rules!」に取り組んだ。
参加者は自分を入れて4人。間が空いてしまったが、今週も安定して参加者がいて助かる。
[学んだこと]
- 16.9. Disambiguating overlapping traits
- トレイトには、同じ名前の関数が定義されていることがしばしばある、そういった場合にどうするか、という話
- 型に複数のトレイトを実装するのは、(前回見たように)それぞれのimplブロックで実装するので、名前が衝突しない
- 関数を呼び出す場合に問題になる:次のような構造体とトレイトがあるとする
trait UsernameWidget { fn get(&self) -> String; } trait AgeWidget { fn get(&self) -> u8; } struct Form { username: String, age: u8, } impl UsernameWidget for Form { fn get(&self) -> String { self.username.clone() } } impl AgeWidget for Form { fn get(&self) -> u8 { self.age } }
- この時、form.get()を呼び出すと以下のようにコンパイルエラーとなる
fn main() { let form = Form{ username: "rustacean".to_owned(), age: 28, }; println!("{}", form.get()); } // 以下のコンパイルエラー error[E0034]: multiple applicable items in scope --> src/main.rs:38:25 | 38 | println!("{}", form.get()); | ^^^ multiple `get` found | note: candidate #1 is defined in an impl of the trait `UsernameWidget` for the type `Form` --> src/main.rs:18:5 | 18 | fn get(&self) -> String { | ^^^^^^^^^^^^^^^^^^^^^^^ note: candidate #2 is defined in an impl of the trait `AgeWidget` for the type `Form` --> src/main.rs:24:5 | 24 | fn get(&self) -> u8 { | ^^^^^^^^^^^^^^^^^^^ help: disambiguate the associated function for candidate #1 | 38 | println!("{}", UsernameWidget::get(&form)); | ~~~~~~~~~~~~~~~~~~~~~~~~~~ help: disambiguate the associated function for candidate #2 | 38 | println!("{}", AgeWidget::get(&form)); | ~~~~~~~~~~~~~~~~~~~~~
- "{}"のフォーマットが、intかStringか決定できないからエラーになるのか?と思い、型の決まった変数に束縛しようとしてみたが、いずれも上と同じエラーに終わった
fn main() { let form = Form{ username: "rustacean".to_owned(), age: 28, }; let name: String = form.get(); }
- テキストでは、次のようにFully Qualifies Syntaxを使っていた
fn main() { let form = Form{ username: "rustacean".to_owned(), age: 28, }; let username = <Form as UsernameWidget>::get(&form); assert_eq!("rustacean".to_owned(), username); let age = <Form as AgeWidget>::get(&form); assert_eq!(28, age); }
- 先ほどのコンパイルエラーのメッセージにあるように、トレイトを使った記法でも動作することが確認できた
- こちらのほうがシンプルで良さそう。
fn main() { let form = Form{ username: "rustacean".to_owned(), age: 28, }; let username = UsernameWidget::get(&form); assert_eq!("rustacean".to_owned(), username); let age = AgeWidget::get(&form); assert_eq!(28, age); }
- 試してみたところ、トレイトに型変換してからget()を呼び出してもうまくいった
- 行数も増えるのであまり使いたくはないが...
fn main() { let form = Form{ username: "rustacean".to_owned(), age: 28, }; let username_widget_form: &dyn UsernameWidget = &form; let username: String = username_widget_form.get(); assert_eq!("rustacean".to_owned(), username); }
- 17. macro_rules!
- メタプログラミングを可能にするマクロシステムの紹介。
- マクロ(println!()みたいなやつ)は関数呼び出しとは異なり、ソースコード中に展開されて、コンパイルされる
- マクロを作成するときはmacro_rules!というマクロを利用して次のように実装する
// `say_hello`というマクロ macro_rules! say_hello { // 引数をとらない () => { // マクロはこのブロック内の内容に展開される println!("Hello!"); }; } fn main() { // この呼び出しは`println!("Hello");`に置き換えられる say_hello!() }
- マクロのメリットは以下の通り(詳細は次回以降)
- 繰り返しを避ける
- ドメイン言語の実装
- 可変長引数のインターフェース
[まとめ]
モブプログラミングスタイルでRust dojoを開催した。
長かったトレイト編が終わってマクロ編がスタート。
ドメイン言語の実装とかがどういうことができるのか、楽しみ。
今週のプルリクエストはこちら。