Rust 宣言マクロ小ネタ集【光編① マクロを作る時に便利なマクロ】


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

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

今回から数回にわたり、重要ではないけど知っておくとちょっとお得かもしれない宣言マクロの小ネタを軽く紹介したいと思います。

以下、今回の出典元です。

[Debugging - The Little Book of Rust Macros](https://veykril.github.io/tlborm/decl-macros/minutiae/debugging.html)

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

# 超有能文字列化マクロ `stringify`

ここまでの記事で何度か登場していますが、与えられたトークン木をそのまま `""` で囲まれた文字列にしてくれる `stringify` というマクロがあります。

[stringify in std - Rust](https://doc.rust-lang.org/std/macro.stringify.html)

https://doc.rust-lang.org/std/macro.stringify.html

```rust:Rust
macro_rules! impl_hello {
    ($name:ty) => {
        impl $name {
            fn hello(&self) {
                println!("Hello. My name is {}!", stringify!($name));
            }
        }
    };
}

fn main() {
    struct Hoge;

    impl_hello(Hoge);

    Hoge.hello();
}
```

Playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=9a3768aa8341aea9143d9da298d91427

...便利ですね！特にこれ以上解説することはないのですが、宣言マクロだけではなく手続きマクロでもかなりの頻度で使うマクロなのでここで紹介させていただきました。特に例えば上記で示したような、構造体名などの `ident` をプログラムで出力したいといった時に頻出です。

# マッチした中身を確かめたい

生成されるマクロをデバッグしたい際、 `cargo expand` による方法があることを紹介しました。

[Rustマクロの事前知識④ 「聴診器 cargo-expand」 #Rust - Qiita](https://qiita.com/namn1125/items/2ad7ab31085b99b586e7)

https://qiita.com/namn1125/items/2ad7ab31085b99b586e7

それ以外にも2つ、コンパイル時にマッチした中身を確認するマクロがあるので、ここで紹介したいと思います。

- [`trace_macros`](https://doc.rust-lang.org/std/macro.trace_macros.html)
- [`log_syntax`](https://doc.rust-lang.org/std/macro.log_syntax.html)

まぁどちらも nightly (実験機能) なのですが :upside_down:

## trace_macros

https://doc.rust-lang.org/std/macro.trace_macros.html

マクロが「どのように」展開されていくかを教えてくれるマクロです。

真偽値を渡して有効化する仕組みになっており、 `trace_macros!(true);` から `trace_macros!(false);` で囲まれた範囲の中にあるマクロが展開されていく様子を**コンパイル時に**ログ出力してくれます！

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

macro_rules! each_tt {
    () => {};
    ($_tt:tt $($rest:tt)*) => {each_tt!($($rest)*);};
}

fn main() {
    each_tt!(foo bar baz quux);

    trace_macros!(true); // デバッグON!
    each_tt!(spim wak plee whum);
    trace_macros!(false); // デバッグOFF!

    each_tt!(trom qlip winp xod);
}
```

```plain:ビルド結果
$ cargo +nightly build
   Compiling project v0.1.0 (/path/to/project)
note: trace_macro
  --> src/main.rs:12:5
   |
12 |     each_tt!(spim wak plee whum);
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: expanding `each_tt! { spim wak plee whum }`
   = note: to `each_tt! (wak plee whum);`
   = note: expanding `each_tt! { wak plee whum }`
   = note: to `each_tt! (plee whum);`
   = note: expanding `each_tt! { plee whum }`
   = note: to `each_tt! (whum);`
   = note: expanding `each_tt! { whum }`
   = note: to `each_tt! ();`
   = note: expanding `each_tt! {  }`
   = note: to ``

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.49s
```

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

マクロの再帰的な展開もトレースしてくれるので、[前回](https://qiita.com/namn1125/items/3439dcab18e6d6d86554)見せたような再帰を利用したマクロがどう展開されていくかをデバッグするのに便利です！

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

macro_rules! list {
    ( $v:expr ) => {
        Rc::new(Cons($v, Rc::new( Nil )))
    };
    ( $v:expr, $($rest:expr),* ) => {
        Rc::new(Cons($v, list!($($rest),*) ))
    };
}

#[allow(dead_code)]
#[derive(Debug)]
enum List {
    Cons(i32, Rc<List>),
    Nil,
}

use std::rc::Rc;
use List::{Cons, Nil};

fn main() {
    trace_macros!(true);
    let _ = list![1, 2, 3, 4, 5];
    trace_macros!(false);
}
```

```plain:展開結果
$ cargo +nightly build
   Compiling project v0.1.0 (/path/to/project)
note: trace_macro
  --> src/main.rs:24:13
   |
24 |     let _ = list![1, 2, 3, 4, 5];
   |             ^^^^^^^^^^^^^^^^^^^^
   |
   = note: expanding `list! { 1, 2, 3, 4, 5 }`
   = note: to `Rc :: new(Cons(1, list! (2, 3, 4, 5)))`
   = note: expanding `list! { 2, 3, 4, 5 }`
   = note: to `Rc :: new(Cons(2, list! (3, 4, 5)))`
   = note: expanding `list! { 3, 4, 5 }`
   = note: to `Rc :: new(Cons(3, list! (4, 5)))`
   = note: expanding `list! { 4, 5 }`
   = note: to `Rc :: new(Cons(4, list! (5)))`
   = note: expanding `list! { 5 }`
   = note: to `Rc :: new(Cons(5, Rc :: new(Nil)))`

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.11s
```

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

## log_syntax

[log_syntax in std - Rust](https://doc.rust-lang.org/std/macro.log_syntax.html)

https://doc.rust-lang.org/std/macro.log_syntax.html

受け取ったメタ変数(というかトークン木...？)を**コンパイル時に**そのままログ出力してくれるマクロです！

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

macro_rules! list {
    ( $v:expr ) => {
        {
            log_syntax!($v);
            Rc::new(Cons($v, Rc::new( Nil )))
        }
    };
    ( $v:expr, $($rest:expr),* ) => {
        {
            log_syntax!($v);
            Rc::new(Cons($v, list!($($rest),*) ))
        }
    };
}

#[allow(dead_code)]
#[derive(Debug)]
enum List {
    Cons(i32, Rc<List>),
    Nil,
}

use std::rc::Rc;
use List::{Cons, Nil};

fn main() {
    let _ = list![1, 2, 3, 4, 5];
}
```

```plain:展開結果
$ cargo +nightly build
   Compiling project v0.1.0 (/path/to/project)
1
2
3
4
5
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.12s
```

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

正直使い所わからんかも...局所的に確認したい時は `trace_macros` より便利かもしれないですね

# まとめ

マクロを書く時に便利そうなマクロを紹介しました！

- [stringify マクロ](https://doc.rust-lang.org/std/macro.stringify.html)
- [trace_macros マクロ](https://doc.rust-lang.org/std/macro.trace_macros.html)
- [log_syntax マクロ](https://doc.rust-lang.org/std/macro.log_syntax.html)

[`cargo expand`](https://github.com/dtolnay/cargo-expand) と併用して使用するとマクロが幾分か楽に書けるようになるかもしれません、多分...！