blog.dopana

Back

Rust中級シリーズの最終回(第16回)。Rustにおける「マクロ」とは、メタプログラミング(コードを生成するコードを書く手法) を実現するための機能群を指します。

実行時に値を受け取る通常の関数と異なり、マクロはソースコードの構文トークンを受け取り、コンパイラが型検査やバイナリ生成を行う前にコードを展開します。

graph TD
    A["Rustのマクロシステム"] --> B["宣言的マクロ (Declarative)<br/>macro_rules!"]
    A --> C["手続き型マクロ (Procedural)<br/>TokenStreamを操作"]
    C --> D["カスタムDerive<br/>#[derive(CustomTrait)]"]
    C --> E["属性風 (Attribute-like)<br/>#[route(GET, '/')]"]
    C --> F["関数風 (Function-like)<br/>sql!(...)"]

10歳でもわかる説明(ELI5):3Dコードプリンター#

関数(Function) と マクロ(Macro) の違い:

  • 関数:ジューサーにオレンジを入れると、お店の営業中(実行時/Runtime)にフレッシュジュースを作ってくれる仕組みです。
  • マクロ:工場がオープンする前(コンパイル時/Compile-time)に、3Dプリンターでジューサーそのものを組み立ててくれる仕組みです。

1. macro_rules! による宣言的マクロ#

Rustで最も広く使われているマクロです。match 式とよく似た構文パターンマッチングの仕組みを持っています:

標準ライブラリの vec! マクロをシンプルに再現した例:

構文のポイント:#

  • $x:expr:任意のRust式(Expression)にマッチし、$x にキャプチャします。
  • $( ... ),*:カンマ区切りで0回以上の繰り返し(*)にマッチします。

2. 手続き型マクロ(Procedural Macros)#

宣言的マクロがパターンの置換であるのに対し、手続き型マクロはトークン列(TokenStream)を入力として受け取り、任意のRustコードを実行して新しい TokenStream を出力する関数です。

以下の3種類が存在します:

A. カスタム Derive マクロ#

#[derive(MyTrait)] でトレイトの実装コードを自動生成します:

// マクロ定義(専用の proc-macro クレート内)
#[proc_macro_derive(HelloMacro)]
pub fn hello_macro_derive(input: TokenStream) -> TokenStream {
    // ASTトークンを解析して impl HelloMacro for StructName を生成
}

// 利用側
#[derive(HelloMacro)]
struct Pancakes;
rust

B. 属性風(Attribute-like)マクロ#

構造体や関数などに付与するカスタム属性を作成します:

#[route(GET, "/users")]
fn get_users() {
    // Webルーティングの定義
}
rust

C. 関数風(Function-like)マクロ#

関数呼び出しのように見えますが、独自の構文を解析してコードを生成できます:

let sql = sql!(SELECT * FROM users WHERE id = 1);
rust

関数とマクロの比較#

項目関数 (Functions)マクロ (Macros)
実行フェーズ実行時 (Runtime)コンパイル時 (Compile-time)
引数の数固定の型・引数の数可変長引数(任意の数の引数を取れる)
操作対象実行時の値ソースコードの構文トークン (AST)
複雑さシンプルで保守しやすい構文が複雑でデバッグが難解

まとめ#

  • マクロは実行時オーバーヘッドなしにコンパイル時のコード自動生成を実現します。
  • macro_rules! は手軽に宣言的パターンマッチングを行える標準的な方法です。
  • 手続き型マクロはカスタムDerive、属性風、関数風を通じてRustの構文木を直接プログラミング可能にします。

参考文献#