3 分で読了 #rust #マクロシステム
このページの目次

プロシージャルマクロ (Procedural Macros)

プロシージャルマクロはコンパイル時にRustコードを実行し、TokenStreamを受け取ってTokenStreamを出力します。deriveマクロはstruct/enumに対してimplを自動生成し、attributeマクロは任意の項目を修飾し、function-likeマクロは関数のように呼び出されますがトークンストリームを処理します。synはTokenStreamをASTに解析し、quoteはASTをTokenStreamに戻し、cargo expandは展開結果を確認します。これらはプロシージャルマクロを書くための標準的なワークフローです。

3種類のプロシージャルマクロ

// 1. derive: #[derive(MyTrait)] — 型に対してimplを自動生成
#[proc_macro_derive(MyTrait)]
pub fn my_derive(input: TokenStream) -> TokenStream { ... }

// 2. attribute: #[my_attr] — 項目全体 (fn, struct, impl) を修飾
#[proc_macro_attribute]
pub fn my_attr(attr: TokenStream, item: TokenStream) -> TokenStream { ... }

// 3. function-like: my_macro!(...) — macro_rules! に似ているが、Rust関数で入力を処理
#[proc_macro]
pub fn my_macro(input: TokenStream) -> TokenStream { ... }

プロシージャルマクロはRustコードであり、コンパイル時に実行され、TokenStream(トークンのシーケンス)を受け取って生成します。これは macro_rules! よりも柔軟性が高く、任意の複雑なコンパイル時の計算を行うことができます。

syn + quote: 事実上の標準

use proc_macro::TokenStream;
use syn::{parse_macro_input, DeriveInput};
use quote::quote;

#[proc_macro_derive(Hello)]
pub fn hello_derive(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);   // TokenStream → AST
    let name = input.ident;
    let gen = quote! {
        impl Hello for #name {
            fn hello() { println!("Hello, {}!", stringify!(#name)); }
        }
    };
    gen.into()                                               // TokenStreamに戻る
}

synTokenStream を AST(Rustコンパイラのフロントエンドのようなもの)に解析します。quote! はRustコードをトークンストリームに戻し、補間(#name)をサポートします。

proc-macro crate の制限

  • 別のcrateでなければならない(Cargo.toml: [lib] proc-macro = true
  • procマクロ関数のみをエクスポートでき、通常の関数、型、定数はエクスポートできない
  • 依存関係にサイクルを含めることはできない——proc-macro crate は他のcrateに依存できるが、同じワークスペースのcrateから逆依存されることはできない

cargo expand: マクロの展開

cargo install cargo-expand
cargo expand                          # 現在のcrateのすべてのマクロを展開
cargo expand my_function              # 特定の関数のみ展開

マクロの動作を理解するための最速の方法です——コンパイラが実際に処理しているコードを見ることができます。

参考

  • syn: docs.rs/syn, quote: docs.rs/quote
  • cargo-expand: github.com/dtolnay/cargo-expand

キーワード: proc macro, derive, syn, quote, TokenStream, cargo expand, attribute macro, function-like macro