Rust dojo第53回を開催した

一週空いて第53回です。前回はこちら。

kdnakt.hatenablog.com

 

 

[第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!()みたいなやつ)は関数呼び出しとは異なり、ソースコード中に展開されて、コンパイルされる
    • C言語などの場合は、文字列で処理されるため演算子の優先順位が問題になったりするが、Rustの場合は抽象構文木へ展開されるのでそうした問題は発生しない
  • マクロを作成するときはmacro_rules!というマクロを利用して次のように実装する
// `say_hello`というマクロ
macro_rules! say_hello {
    // 引数をとらない
    () => { // マクロはこのブロック内の内容に展開される
        println!("Hello!");
    };
}

fn main() {
    // この呼び出しは`println!("Hello");`に置き換えられる
    say_hello!()
}
  • マクロのメリットは以下の通り(詳細は次回以降)
    • 繰り返しを避ける
    • ドメイン言語の実装
    • 可変長引数のインターフェース

 

[まとめ]

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

長かったトレイト編が終わってマクロ編がスタート。

ドメイン言語の実装とかがどういうことができるのか、楽しみ。

 

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

github.com