Rust 宣言マクロ小ネタ集【光編② メタ変数式】


こちらの記事は [Rustマクロ冬期講習アドベントカレンダー](https://qiita.com/advent-calendar/2024/rust-macro) 10日目の記事です！

> アドカレまとめ記事はこちら！: [Rustマクロ作成チートシート！](https://qiita.com/namn1125/items/d601d2cbbf2cc7b5d3dc)

前回に続き小ネタを紹介したいと思います。以下、今回の出典元です。

[Metavariable Expressions - The Little Book of Rust Macros](https://veykril.github.io/tlborm/decl-macros/minutiae/metavar-expr.html)

https://veykril.github.io/tlborm/decl-macros/minutiae/metavar-expr.html

今回は、宣言マクロの展開先に使える特殊な記法「メタ変数式」に触れたいと思います。

宣言マクロで展開先においてメタ変数またはそれと同じ個数の何かを設置する方法は

- 普通の設置 `$メタ変数`
- 繰り返し構文 `$(...)*`

の2つでした。実はそれ以外にも展開先で扱える記法・情報がありそれがメタ変数式なのですが、使い所が微妙なものばかりだったので小ネタとして扱います。工夫すれば案外使えるかも...？<font color="#aaaaa">でもnightlyなんだよなぁ...</font>

紹介するメタ変数式リスト(説明しやすい順に並べ替えています)

- [`$$`](#%E3%83%89%E3%83%AB%E3%82%92%E3%81%9D%E3%81%AE%E3%81%BE%E3%81%BE%E5%87%BA%E5%8A%9B%E3%81%99%E3%82%8B-)
- [`${index(depth)}`](#%E4%BB%8A%E4%BD%95%E5%95%8F%E7%9B%AE-indexdepth)
- [`${ignore($ident)}`](#%E3%83%A1%E3%82%BF%E5%A4%89%E6%95%B0%E3%82%92%E7%84%A1%E8%A6%96-ignoreident)
- [`${len(depth)}`](#%E7%B9%B0%E3%82%8A%E8%BF%94%E3%81%97%E5%85%A8%E4%BD%93%E3%81%AE%E9%95%B7%E3%81%95-lendepth)
- [`${count($ident, depth)}`](#%E3%83%A1%E3%82%BF%E5%A4%89%E6%95%B0%E3%81%AE%E5%90%88%E8%A8%88%E5%87%BA%E7%8F%BE%E5%9B%9E%E6%95%B0-countident-depth)

Little Bookに合わせているのですが、 `$ident` がメタ変数を指しています。変数名が付けられているからその部分を識別子 `ident` と表現しているのでしょう。

# ドルをそのまま出力する `$$`

ドル `$` をそのまま出力します。つまりエスケープみたいな感じですね。ドルをそのまま使う文法は `macro_rules` ぐらいしかないので、つまり宣言マクロで宣言マクロを出力したい場合なんかに利用します。そんな機会ある...？

以下は[Little Bookにある例](https://veykril.github.io/tlborm/decl-macros/minutiae/metavar-expr.html)ままです。というか今回載せている例は特に断りがない限りLittle Bookのものです。

```rust:Rust
#![feature(macro_metavar_expr)]

macro_rules! foo {
    () => {
        macro_rules! bar {
            ( $$( $$any:tt )* ) => { $$( $$any )* };
        }
    };
}

foo!();
bar!();
```

`foo` マクロによって `bar` マクロが定義されて、実行できるようになります。

# 今何問目？ `${index(depth)}`

繰り返し構文 `$(...)*` において、現在のインデックスを得るメタ変数式です。実は繰り返し構文はネストが可能で、 `depth` にはどの深さのインデックスを取得するかを指定します。省略した場合、つまり `${index()}` と書いた場合は最上層、つまり `${index(0)}` だと見做されます。

実用例だと例えばこんな感じ...わざわざマクロ使う理由？ねーよ！！！！

```rust:Rust
#![feature(macro_metavar_expr)]

macro_rules! square_map {
    ( $( $($value:expr),* );*) => {
        vec![
            $(
                $(
                    ((${index()}, ${index(1)}), $value)
                ),*
            ),*
        ]
    }
}

use std::collections::HashMap;

fn main() {
    let o = true;
    let x = false;

    let treasure_map: HashMap<(usize, usize), bool> = square_map! {
        x, x, x;
        x, o, x;
        x, x, x
    }
    .into_iter()
    .collect();

    for y in 0..4 {
        for x in 0..4 {
            match treasure_map.get(&(x, y)) { // タプルのハッシュで取れて便利でしょ？...そんなことはない...？(涙)
                Some(true) => print!("o "),
                Some(false) => print!("x "),
                None => print!("  "),
            }
        }
        println!();
    }

    dbg!(treasure_map);
}
```

マクロ展開後のvecの中身はこんな感じになっています。

```plain
((0, 0), x),
((1, 0), x),
((2, 0), x),
((0, 1), x),
((1, 1), o),
((2, 1), x),
((0, 2), x),
((1, 2), x),
((2, 2), x),
```

Playground: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=d3d7a3ebe1951cb625f36faa256ecbfa

...筆者は良い例が思いつかなかったですが、利用方法次第では便利かもです。

# メタ変数を無視！ `${ignore($ident)}`

展開予定のメタ変数を受け取り、何も出力しないメタ変数式です。

これ単体だと無意味ですが、繰り返し構文中で他のメタ変数式の情報だけ活用したい場合用に用いるようです。

必要性がわかるような気がする一方需要が不明すぎる

```rust:Rust
#![feature(macro_metavar_expr)]

macro_rules! repetition_tuples {
    ( $( ( $( $inner:ident ),* ) ; )* ) => {
        ($(
            $(
                (
                    ${index()},
                    ${index(1)}
                    ${ignore($inner)} // この行を消すと何故か動かない
                ),
            )*
        )*)
    };
}

fn main() {
    let tuple = repetition_tuples!(
        ( one, two ) ;
        () ;
        ( one ) ;
        ( one, two, three ) ;
    );
    println!("{tuple:?}");
}
```

```plain:実行結果
((0, 0), (1, 0), (0, 2), (0, 3), (1, 3), (2, 3))
```

Playground: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=5c7caac3fb61af69a1e9d71f82912630

あと2つ紹介予定なのですが正直違いがわかりにくいです！！><

# 繰り返し全体の長さ `${len(depth)}`

インデックス取得があるなら、繰り返し全体の回数を得る方法もあるでしょう。というわけでそれが `len` [^len] メタ変数式です！

[^len]: Little Bookでは `length` になっていますが、どうやらその後 `len` に変わったようです。

`index` の時と同じく `depth` で繰り返しの深さを指定します。

```rust:Rust
#![feature(macro_metavar_expr)]

macro_rules! lets_count {
    ( $( $outer:ident ( $( $inner:ident ),* ) ; )* ) => {
        $(
            $(
                println!(
                    "'{}' in inner iteration {}/{} with '{}' in outer iteration {}/{} ",
                    stringify!($inner), ${index()}, ${len()},
                    stringify!($outer), ${index(1)}, ${len(1)},
                );
            )*
        )*
    };
}

fn main() {
    lets_count!(
        many (small , things) ;
        none () ;
        exactly ( one ) ;
    );
}
```

繰り返し全体をイテレータかベクトルか何かと思えばその長さなのでわかりやすいかと思います。

```plain:出力
'small' in inner iteration 0/2 with 'many' in outer iteration 0/3 
'things' in inner iteration 1/2 with 'many' in outer iteration 0/3 
'one' in inner iteration 0/1 with 'exactly' in outer iteration 2/3
```

Playground: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=4b582e4e00afaf2909825b30252bb20b

# メタ変数の合計出現回数 `${count($ident, depth)}`

長さと言うよりはメタ変数の出現回数をカウントするもののようですが、はっきりいいます。 **挙動が謎です** 。

```rust:Rust
#![feature(macro_metavar_expr)]

macro_rules! foo {
    ( $( $outer:ident ( $( $inner:ident ),* ) ; )* ) => {
        println!("count(outer, 0): $outer repeats {} times", ${count($outer)});
        println!("count(inner, 0): The $inner repetition repeats {} times in the outer repetition", ${count($inner, 0)});
        println!("count(inner, 1): $inner repeats {} times in the inner repetitions", ${count($inner, 1)});
    };
}

fn main() {
    foo! {
        outer () ;
        outer ( inner , inner ) ;
        outer () ;
        outer ( inner ) ;
    };
}
```

```plain:出力
count(outer, 0): $outer repeats 4 times
count(inner, 0): The $inner repetition repeats 3 times in the outer repetition
count(inner, 1): $inner repeats 4 times in the inner repetitions
```

Playground: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=ed5eeedc427a05fc425db42e4d304f42

1つ目と2つ目まではまだわかります。全体での出現回数になっており、確かにこれは `len` メタ変数式で得られる情報とはまた異なるものです。しかし3つ目の例がよくわかりません。深さに関係なく `inner` の合計マッチ回数は3回のはずですが、4になっています。 `inner` が何回出てきたかではなく `( ... )` が何回出てきたかを出力しているのでしょうか？ともかく、`depth = 1` の時の挙動がよくわからないですね...

# まとめ・所感

- [`$$`](#%E3%83%89%E3%83%AB%E3%82%92%E3%81%9D%E3%81%AE%E3%81%BE%E3%81%BE%E5%87%BA%E5%8A%9B%E3%81%99%E3%82%8B-)
- [`${index(depth)}`](#%E4%BB%8A%E4%BD%95%E5%95%8F%E7%9B%AE-indexdepth)
- [`${ignore($ident)}`](#%E3%83%A1%E3%82%BF%E5%A4%89%E6%95%B0%E3%82%92%E7%84%A1%E8%A6%96-ignoreident)
- [`${len(depth)}`](#%E7%B9%B0%E3%82%8A%E8%BF%94%E3%81%97%E5%85%A8%E4%BD%93%E3%81%AE%E9%95%B7%E3%81%95-lendepth)
- [`${count($ident, depth)}`](#%E3%83%A1%E3%82%BF%E5%A4%89%E6%95%B0%E3%81%AE%E5%90%88%E8%A8%88%E5%87%BA%E7%8F%BE%E5%9B%9E%E6%95%B0-countident-depth)

使い所がイマイチなメタ変数式について解説しました、小ネタ程度に捉えていただけると幸いです！