こちらの記事は Rustマクロ冬期講習アドベントカレンダー 11日目の記事です!
アドカレまとめ記事はこちら!: Rustマクロ作成チートシート!
またまた前回に続き小ネタを紹介したいと思います。以下、今回の出典元です。
Callbacks - The Little Book of Rust Macros
「マクロのコールバック」の話をしますが、今回も、需要が、謎です!!!小ネタすぎる
今回問題となるマクロです。
macro_rules! 敬語に直すマクロ {
    (式だね) => { println!("式ですね"); };
    ($($other:tt)*) => { println!("式ではなさそうです"); };
}
macro_rules! 式か判別するマクロ {
    ( $_:expr ) => { 式だね };
    ( $_:tt ) => { 式じゃないね };
}
fn main() {
    敬語に直すマクロ!(式か判別するマクロ!(1 + 1));
}
式ではなさそうです
式ではないからこのような結果になったのでしょうか...? Rust 宣言マクロ小ネタ集【光編① マクロを作る時に便利なマクロ】 で紹介した trace_macros を使って展開順を確認してみます。
#![feature(trace_macros)]
macro_rules! 敬語に直すマクロ {
    (式だね) => { println!("式ですね"); };
    ($($other:tt)*) => { println!("式ではなさそうです"); };
}
macro_rules! 式か判別するマクロ {
    ( $_:expr ) => { 式だね };
    ( $_:tt ) => { 式じゃないね };
}
fn main() {
    trace_macros!(true);
    敬語に直すマクロ!(式か判別するマクロ!(1 + 1));
    trace_macros!(false);
}
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! がナチュラルにこれを達成していますが、マクロの 引数 に 式か判別するマクロ が入ってしまっているのが原因です。 出力 の方に入っていれば直感的に展開されます。
macro_rules! 敬語に直すマクロ {
    (式だね) => { println!("式ですね"); }; // ここは書いているとおりになっている
    ($($other:tt)*) => { println!("式ではなさそうです"); };
}
macro_rules! 式か判別するマクロ {
    ( $_:expr ) => { 敬語に直すマクロ!(式だね) }; // つまり、出力に書かれていれば見た目通りに展開される
    ( $_:tt ) => { 敬語に直すマクロ!(式じゃないね) };
}
fn main() {
    式か判別するマクロ!(1 + 1);
}
式ですね
コールバック
「...いや、そしたら式か判別した後の出力が敬語で固定されるじゃん!」はい。そのツッコミを待っていました。わざわざマクロを分けているということは敬語以外のバリエーションも用意したいわけです。
macro_rules! 敬語に直すマクロ {
    (式だね) => { println!("式ですね"); };
    ($($other:tt)*) => { println!("式ではなさそうです"); };
}
macro_rules! 英語に直すマクロ {
    (式だね) => { println!("This is expr."); };
    ($($other:tt)*) => { println!("Unknown."); };
}
macro_rules! 式か判別するマクロ {
    ( $_:expr ) => { 敬語に直すマクロ!(式だね) }; // 英語に直すには...?
    ( $_:tt ) => { 敬語に直すマクロ!(式じゃないね) };
}
fn main() {
    式か判別するマクロ!(1 + 1);
}
要は出力に「敬語に直すマクロ」や「英語に直すマクロ」があればいいわけなので、「式か判別するマクロ」に マクロ名を渡すことで 出力を変更できるようにすればよいです!
#![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);
}
式ですね
式ではなさそうです
This is expr.
Playground: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=1228eec1a74f38431b3054f3f197bb0d
つまりイベントドリブンプログラミングや非同期処理でよく出てくる 「コールバック」 と同じ方法を使うことで、マクロの展開結果を他のマクロに渡すことができます!
まとめ・所感
「だから話はわかるけどどこで使うんだよ!」って内容ですよね。小ネタでした!
