TL;DR

名前のとおり、関数のように呼び出せる Rust の手続き的マクロ。引数のトークン列を受け取り、任意のトークン列を返す。

仕組み

関数的マクロは引数を 1 つだけ持つ。my_func_macro!(15, x + y, f32::consts::PI); のように複数の引数を渡しても、内部では 1 つのトークン列として扱われる。呼び出しの区切り記号の内側がそのまま入力になり、返したトークン列が呼び出し全体と置き換わる。

定義するには、proc-macro = true を設定したクレートで、関数に #[proc_macro] 属性を付ける。

例

受け取ったトークンを出力するだけのマクロは次のようになる。

use proc_macro::TokenStream;
 
#[proc_macro]
pub fn my_func_macro(args: TokenStream) -> TokenStream {
    println!("Input Token Stream is:");
    for tt in args {
        println!("  {tt:?}");
    }
    TokenStream::new()
}

my_func_macro!(15, x + y, f32::consts::PI); を渡すと、次のように出力される。

Input Token Stream is:
  Literal { kind: Integer, symbol: "15", suffix: None, span: #0 bytes(90..92) }
  Punct { ch: ',', spacing: Alone, span: #0 bytes(92..93) }
  Ident { ident: "x", span: #0 bytes(94..95) }
  Punct { ch: '+', spacing: Alone, span: #0 bytes(96..97) }
  Ident { ident: "y", span: #0 bytes(98..99) }
  Punct { ch: ',', spacing: Alone, span: #0 bytes(99..100) }
  Ident { ident: "f32", span: #0 bytes(101..104) }
  Punct { ch: ':', spacing: Joint, span: #0 bytes(104..105) }
  Punct { ch: ':', spacing: Alone, span: #0 bytes(105..106) }
  Ident { ident: "consts", span: #0 bytes(106..112) }
  Punct { ch: ':', spacing: Joint, span: #0 bytes(112..113) }
  Punct { ch: ':', spacing: Alone, span: #0 bytes(113..114) }
  Ident { ident: "PI", span: #0 bytes(114..116) }

注意点

上の出力から分かるように、引数で渡ってくる値は低レベルなので、実装するときは自分でパースする必要がある。たとえば複数の引数を受け取るなら、カンマで区切るために TokenTree::Punct を探さなければならない。syn クレートを使うとパースが楽になるが、それでも宣言的マクロを使うほうが簡単である。一方で、関数的マクロは通常の Rust のコードとしてはパースできない入力も受け取れる柔軟さを持つ。

関連

出典