kdnakt blog

hello there.

Rust dojo第54回を開催した

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

kdnakt.hatenablog.com

 

 

[第54回の様子]

2022/09/28に第54回を開催した。

 

内容としてはRust By Example 日本語版の17. macro_rules!の「17.1. 構文」に取り組んだ。

 

参加者は自分を入れて3人。最近は開催頻度も低いので参加者も少なめ。久しぶりにドライバーを担当した。

 

教材的にはあと半年くらいは続けられそうだけど、そろそろ違う題材にしようかなあ...。

 

[学んだこと]

  • 17.1.1. 識別子
  • マクロの引数は先頭に$がつく
  • マクロの引数の型には識別子designatorsが利用できる
    • block:ブロック
    • expr:式
    • ident:関数や変数の名前
    • literal:リテラル(文字列や数値、真偽値)
    • stmt:宣言
    • などがある。その他に利用可能な識別子の一覧はリファレンス参照
  • 以下はidentを使う例
// 関数を作成するマクロ
macro_rules! create_function {
    ($func_name:ident) => {
        fn $func_name() {
            // stringifyマクロはidentを文字列に変換
            println!("You called {:?}()",
                     stringify!($func_name));
        }
    };
}

create_function!(foo); // fooという関数を作る
create_function!(bar); // barという関数を作る

fn main() {
    foo(); // You called "foo"()
    bar(); // You called "bar"()
}
  • 以下はexprを使う例
// 式の結果を出力するマクロ
macro_rules! print_result {
    ($expression:expr) => {
        println!("{:?} = {:?}",
                 stringify!($expression),
                 $expression);
    };
}

fn main() {
    print_result!(1u32 + 1); // "1u32 + 1" = 2

    // ブロックも式の一種
    print_result!({
        let x = 1u32;

        x * x + 2 * x - 1
    });
    // "{ let x = 1u32; x * x + 2 * x - 1 }" = 2
}
  • 17.1.2. オーバーロード
  • マクロルールはmacro_rules!という複数形の名前で定義することからも分かる通り、異なる引数をとってオーバーロードが可能
    • matchによるパターンマッチのような感じ
macro_rules! test {
    // テンプレートは自由。この場合は「test!(式1; and 式2);」のように呼び出す
    ($left:expr; and $right:expr) => {
        println!("{:?} and {:?} is {:?}",
                 stringify!($left),
                 stringify!($right),
                 $left && $right)
    }; // 末尾にセミコロンが必要
    ($left:expr; or $right:expr) => {
        println!("{:?} or {:?} is {:?}",
                 stringify!($left),
                 stringify!($right),
                 $left || $right)
    };
}

fn main() {
    test!(1i32 + 1 == 2i32; and 2i32 * 2 == 4i32);
    // "1i32 + 1 == 2i32" and "2i32 * 2 == 4i32" is true

    test!(true; or false);
    // "true" or "false" is true
}
  • 折角なので、xorのパターンを実装してみる
macro_rules! test {
    // andとorは省略
    ($left:expr; xor $right:expr) => {
        println!("{:?} xor {:?} is {:?}",
                 stringify!($left),
                 stringify!($right),
                 $left ^ $right)
    };
}

fn main() {
    test!(true; xor false);
    // "true" xor "false" is true
    
    test!(true; xor true);
    // "true" xor "true" is false
}
  • 存在しないパターンを呼び出したらコンパイルエラーとなった(それはそう
macro_rules! test {
    // and, or, xorが定義されている。省略。
}

fn main() {
    // 定義されていないnandを呼び出す
    test!(true; nand false);
}

// 以下のコンパイルエラー
error: no rules expected the token `nand`
  --> src/main.rs:29:17
   |
5  | macro_rules! test {
   | ----------------- when calling this macro
...
29 |     test!(true; nand false);
   |                 ^^^^ no rules expected this token in macro call
  • 17.1.3. 繰り返し
  • マクロの引数は繰り返しを定義できる:可変長引数みたいな感じっぽい
    • マッチ対象の引数を$(...),+で囲むと1回以上の繰り返し
    • マッチ対象の引数を$(...),*で囲むと0回以上の繰り返し
  • サンプルのマクロはこんな感じ
macro_rules! find_min {
    // 基本となるケース
    ($x:expr) => ($x);
    
    // `$x`に少なくとも1つの`$y`が続く場合
    ($x:expr, $($y:expr),+) => (
        // 再帰
        std::cmp::min($x, find_min!($($y),+))
    )
}

fn main() {
    println!("{}", find_min!(1)); // 1
    println!("{}", find_min!(1 + 2, 2)); // 2
    println!("{}", find_min!(5, 2 * 3, 4)); // 4
}
  • ちょっと難しいけど、std::cmp::min($x, find_min!($($y),+))find_min!($($y),+)の部分は、引数の個数に応じて基本ケースか$(...),+のケースかが呼び出されるんだろうな、と理解した。
  • $(...),*を使った実装もやってみようと思ったのだけど、時間が足りず断念。なんかいい例があるかなあ...

 

[まとめ]

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

これでマクロの基本構文はバッチリ、のはず...。

 

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

github.com