Rust 宣言マクロ小ネタ集【コールバック・マクロの展開結果を別なマクロに渡す方法】


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

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

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

https://veykril.github.io/tlborm/decl-macros/patterns/callbacks.html

[Callbacks - The Little Book of Rust Macros](https://veykril.github.io/tlborm/decl-macros/patterns/callbacks.html)

「マクロのコールバック」の話をしますが、今回も、需要が、謎です！！！小ネタすぎる

今回問題となるマクロです。

```rust:Rust
macro_rules! 敬語に直すマクロ {
    (式だね) => { println!("式ですね"); };
    ($($other:tt)*) => { println!("式ではなさそうです"); };
}

macro_rules! 式か判別するマクロ {
    ( $_:expr ) => { 式だね };
    ( $_:tt ) => { 式じゃないね };
}

fn main() {
    敬語に直すマクロ!(式か判別するマクロ!(1 + 1));
}
```

```plain:出力結果
式ではなさそうです
```

式ではないからこのような結果になったのでしょうか...？ [Rust 宣言マクロ小ネタ集【光編① マクロを作る時に便利なマクロ】](https://qiita.com/namn1125/items/df4ccd6e0c16bd94fed0) で紹介した [`trace_macros`](https://doc.rust-lang.org/std/macro.trace_macros.html) を使って展開順を確認してみます。

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

macro_rules! 敬語に直すマクロ {
    (式だね) => { println!("式ですね"); };
    ($($other:tt)*) => { println!("式ではなさそうです"); };
}

macro_rules! 式か判別するマクロ {
    ( $_:expr ) => { 式だね };
    ( $_:tt ) => { 式じゃないね };
}

fn main() {
    trace_macros!(true);
    敬語に直すマクロ!(式か判別するマクロ!(1 + 1));
    trace_macros!(false);
}
```

```plain:順序確認
note: trace_macro
  --> src/main.rs:15:5
   |
15 |     敬語に直すマクロ!(式か判別するマクロ!(1 + 1));
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: expanding `敬語に直すマクロ! { 式か判別するマクロ!(1 + 1) }`
   = note: to `println! ("式ではなさそうです");`
   = note: expanding `println! { "式ではなさそうです" }`
   = note: to `{
               $crate :: io ::
               _print($crate :: format_args_nl! ("式ではなさそうです"));
           }`
```

関数なら内側から評価されますが、 **マクロは外側から展開されていく** ため、 `式か判別するマクロ` が展開される前に `敬語に直すマクロ` のほうの展開が行われてしまいます。つまりこの直感的な書き方ではマクロの展開結果を別なマクロに渡すことができません。では「マクロの展開結果を別なマクロに渡すには？」という話題です。やりたいことはわかるけどやっぱり需要が謎...

# 答え(？): 先に展開したいマクロの「展開結果」に、後で展開したいマクロを含める

`println!` がナチュラルにこれを達成していますが、マクロの **引数** に `式か判別するマクロ` が入ってしまっているのが原因です。 **出力** の方に入っていれば直感的に展開されます。

```rust:Rust
macro_rules! 敬語に直すマクロ {
    (式だね) => { println!("式ですね"); }; // ここは書いているとおりになっている
    ($($other:tt)*) => { println!("式ではなさそうです"); };
}

macro_rules! 式か判別するマクロ {
    ( $_:expr ) => { 敬語に直すマクロ!(式だね) }; // つまり、出力に書かれていれば見た目通りに展開される
    ( $_:tt ) => { 敬語に直すマクロ!(式じゃないね) };
}

fn main() {
    式か判別するマクロ!(1 + 1);
}
```

```plain:出力結果
式ですね
```

# コールバック

「...いや、そしたら式か判別した後の出力が敬語で固定されるじゃん！」はい。そのツッコミを待っていました。わざわざマクロを分けているということは敬語以外のバリエーションも用意したいわけです。

```rust:Rust
macro_rules! 敬語に直すマクロ {
    (式だね) => { println!("式ですね"); };
    ($($other:tt)*) => { println!("式ではなさそうです"); };
}

macro_rules! 英語に直すマクロ {
    (式だね) => { println!("This is expr."); };
    ($($other:tt)*) => { println!("Unknown."); };
}

macro_rules! 式か判別するマクロ {
    ( $_:expr ) => { 敬語に直すマクロ!(式だね) }; // 英語に直すには...？
    ( $_:tt ) => { 敬語に直すマクロ!(式じゃないね) };
}

fn main() {
    式か判別するマクロ!(1 + 1);
}
```

要は出力に「敬語に直すマクロ」や「英語に直すマクロ」があればいいわけなので、「式か判別するマクロ」に **マクロ名を渡すことで** 出力を変更できるようにすればよいです！

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

macro_rules! 敬語に直すマクロ {
    (式だね) => { println!("式ですね"); };
    ($($other:tt)*) => { println!("式ではなさそうです"); };
}

macro_rules! 英語に直すマクロ {
    (式だね) => { println!("This is expr."); };
    ($($other:tt)*) => { println!("Unknown."); };
}

macro_rules! 式か判別するマクロ {
    ( $callback:ident; $_:expr ) => { $callback ! (式だね) };
    ( $callback:ident; $_:tt ) => { $callback ! (式じゃないね) };
}

fn main() {
    trace_macros!(true);
    式か判別するマクロ!(敬語に直すマクロ; 1 + 1);
    式か判別するマクロ!(敬語に直すマクロ; @);
    式か判別するマクロ!(英語に直すマクロ; if true { () } else { () });
    trace_macros!(false);
}
```

```plain:出力結果
式ですね
式ではなさそうです
This is expr.
```

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

つまりイベントドリブンプログラミングや非同期処理でよく出てくる **「コールバック」** と同じ方法を使うことで、マクロの展開結果を他のマクロに渡すことができます！

# まとめ・所感

「だから話はわかるけどどこで使うんだよ！」って内容ですよね。小ネタでした！