一週空いて第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を開催した。
長かったトレイト編が終わってマクロ編がスタート。
ドメイン言語の実装とかがどういうことができるのか、楽しみ。
今週のプルリクエストはこちら。