第19章: マクロ - コードを生成するコード

学習目標

  • マクロとは何か、なぜ必要なのかを理解する
  • 宣言的マクロ(macro_rules!)の書き方を習得する
  • 手続き的マクロの3種類を知る
  • マクロ展開の仕組みとデバッグ方法を学ぶ
  • 実践的なDSL(ドメイン特化言語)を作成する
  • ---

    19.1 マクロの必要性と基礎概念

    19.1.1 マクロとは何か

    マクロ(Macro)は、コードを生成するコードです。コンパイル時に展開され、実際のRustコードに変換されます。

    ┌─────────────────────────────────────────────────────────┐
    │              マクロ vs 関数の違い                        │
    ├─────────────────────────────────────────────────────────┤
    │                                                         │
    │  【関数】                        【マクロ】              │
    │                                                         │
    │  ✓ 実行時に呼ばれる              ✓ コンパイル時に展開    │
    │  ✓ 型が固定                     ✓ 任意の数の引数        │
    │  ✓ 引数は式のみ                 ✓ 式、文、型も受け取れる │
    │  ✗ 可変長引数は難しい            ✓ 可変長引数が簡単      │
    │  ✗ 新しい構文を作れない          ✓ DSLを作れる          │
    │                                                         │
    └─────────────────────────────────────────────────────────┘
    

    19.1.2 既に使っているマクロ

    実は、あなたはすでに多くのマクロを使っています:

    // すべてマクロ!(末尾の!が目印)
    println!("Hello, world!");
    vec![1, 2, 3];
    panic!("エラー発生");
    assert_eq!(a, b);
    

    関数ではなくマクロである理由

    // println!は可変長引数が必要
    println!("値: {}", x);              // 2引数
    println!("値: {}, {}", x, y);       // 3引数
    println!("値: {}, {}, {}", x, y, z); // 4引数
    
    // vec!は任意個の要素を受け取る
    vec![1];              // 1要素
    vec![1, 2];           // 2要素
    vec![1, 2, 3, 4, 5];  // 5要素
    

    ---

    19.2 宣言的マクロ(macro_rules!)

    19.2.1 基本構文

    宣言的マクロはパターンマッチングに基づいています:

    macro_rules! マクロ名 {
        (パターン1) => {
            展開コード1
        };
        (パターン2) => {
            展開コード2
        };
    }
    

    最もシンプルな例

    macro_rules! say_hello {
        () => {
            println!("Hello, world!");
        };
    }
    
    // 使用
    say_hello!();  // => println!("Hello, world!");
    

    19.2.2 引数を受け取るマクロ

    式を受け取る

    macro_rules! double {
        ($x:expr) => {
            $x * 2
        };
    }
    
    fn main() {
        let result = double!(5);     // => 5 * 2 = 10
        let result = double!(2 + 3); // => (2 + 3) * 2 = 10
        println!("{}", result);
    }
    

    メタ変数の種類

    指定子 マッチするもの
    `expr` `1 + 2`, `func()`
    `stmt` `let x = 5;`
    `ty` `i32`, `Vec`
    `ident` 識別子 `foo`, `bar`
    `path` パス `std::vec::Vec`
    `literal` リテラル `42`, `"hello"`
    `block` ブロック `{ ... }`
    `item` アイテム `fn f() {}`, `struct S;`
    `pat` パターン `Some(x)`

    19.2.3 複数パターンのマッチング

    macro_rules! create_function {
        // パターン1: 引数なし
        ($func_name:ident) => {
            fn $func_name() {
                println!("{}が呼ばれました", stringify!($func_name));
            }
        };
    
        // パターン2: 引数あり
        ($func_name:ident, $arg:ident : $arg_ty:ty) => {
            fn $func_name($arg: $arg_ty) {
                println!("{}({})が呼ばれました",
                         stringify!($func_name), $arg);
            }
        };
    }
    
    // 使用
    create_function!(foo);
    create_function!(bar, x: i32);
    
    fn main() {
        foo();      // => "fooが呼ばれました"
        bar(42);    // => "bar(42)が呼ばれました"
    }
    

    19.2.4 繰り返しパターン

    可変長引数を処理する

    macro_rules! vec_literal {
        // 繰り返し: $($x:expr),*
        // $(...),* は「カンマ区切りで0回以上繰り返し」
        ($($x:expr),*) => {
            {
                let mut temp_vec = Vec::new();
                $(
                    temp_vec.push($x);
                )*
                temp_vec
            }
        };
    }
    
    fn main() {
        let v = vec_literal![1, 2, 3, 4, 5];
        println!("{:?}", v);  // [1, 2, 3, 4, 5]
    }
    

    繰り返しの構文

  • $(...) : 0回以上繰り返し
  • $(...)+ : 1回以上繰り返し
  • $(...), : カンマ区切りで0回以上
  • $(...),+ : カンマ区切りで1回以上
  • $(...) ; : セミコロン区切り

---

19.3 実践的なマクロの例

19.3.1 HashMap初期化マクロ

macro_rules! hashmap {
    // 空のマップ
    () => {
        std::collections::HashMap::new()
    };

    // キー:値のペアを受け取る
    ($($key:expr => $val:expr),* $(,)?) => {
        {
            let mut map = std::collections::HashMap::new();
            $(
                map.insert($key, $val);
            )*
            map
        }
    };
}

use std::collections::HashMap;

fn main() {
    // 空のマップ
    let empty: HashMap<i32, i32> = hashmap!();

    // 初期値付き
    let scores = hashmap! {
        "Alice" => 95,
        "Bob" => 87,
        "Charlie" => 92,
    };

    println!("{:?}", scores);
}

ポイント

  • $(,)? : 末尾のカンマをオプションにする
  • ブロック { ... } で囲むことで、一時変数がスコープ外に漏れない
  • 19.3.2 計測マクロ

    macro_rules! measure {
        ($name:expr, $block:block) => {
            {
                let start = std::time::Instant::now();
                let result = $block;
                let duration = start.elapsed();
                println!("{}: {:?}", $name, duration);
                result
            }
        };
    }
    
    fn main() {
        let result = measure!("重い計算", {
            let mut sum = 0;
            for i in 0..1_000_000 {
                sum += i;
            }
            sum
        });
    
        println!("結果: {}", result);
    }
    

    19.3.3 テストケース生成マクロ

    macro_rules! test_cases {
        ($test_name:ident: $func:ident($($input:expr),*) => $expected:expr) => {
            #[test]
            fn $test_name() {
                assert_eq!($func($($input),*), $expected);
            }
        };
    }
    
    fn add(a: i32, b: i32) -> i32 {
        a + b
    }
    
    // テストケースを生成
    test_cases!(test_add_positive: add(2, 3) => 5);
    test_cases!(test_add_negative: add(-1, -2) => -3);
    test_cases!(test_add_zero: add(0, 0) => 0);
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        // 上記のマクロがこのようなコードに展開される:
        // #[test]
        // fn test_add_positive() {
        //     assert_eq!(add(2, 3), 5);
        // }
    }
    

    ---

    19.4 マクロのデバッグとトレース

    19.4.1 マクロ展開の確認

    # マクロ展開後のコードを確認
    cargo rustc -- -Z unstable-options --pretty=expanded
    
    # またはcargo-expandを使用
    cargo install cargo-expand
    cargo expand
    

    19.4.2 デバッグマクロ

    // stringify!: 式をそのまま文字列に
    macro_rules! debug_print {
        ($val:expr) => {
            println!("{} = {:?}", stringify!($val), $val);
        };
    }
    
    fn main() {
        let x = 42;
        debug_print!(x);      // => "x = 42"
        debug_print!(x + 10); // => "x + 10 = 52"
    }
    

    便利な組み込みマクロ

    // file!(), line!(), column!(): ソースコード位置
    macro_rules! log {
        ($msg:expr) => {
            println!("[{}:{}] {}", file!(), line!(), $msg);
        };
    }
    
    fn main() {
        log!("デバッグメッセージ");
        // => "[src/main.rs:10] デバッグメッセージ"
    }
    

    ---

    19.5 手続き的マクロ概要

    19.5.1 手続き的マクロの3種類

    手続き的マクロはRustのコードで実装されるマクロです:

    ┌─────────────────────────────────────────────────────────┐
    │           手続き的マクロの3種類                           │
    ├─────────────────────────────────────────────────────────┤
    │                                                         │
    │  1. カスタム derive マクロ                               │
    │     #[derive(MyTrait)]                                  │
    │     struct MyStruct { ... }                             │
    │                                                         │
    │  2. 属性マクロ                                           │
    │     #[my_attribute]                                     │
    │     fn my_function() { ... }                            │
    │                                                         │
    │  3. 関数風マクロ                                         │
    │     my_macro!(...)                                      │
    │                                                         │
    └─────────────────────────────────────────────────────────┘
    

    19.5.2 カスタム derive マクロの例

    目標:#[derive(Builder)]を実装する

    // 使用例(理想形)
    #[derive(Builder)]
    struct User {
        name: String,
        age: u32,
        email: Option<String>,
    }
    
    // これが自動生成される:
    // impl User {
    //     fn builder() -> UserBuilder { ... }
    // }
    //
    // struct UserBuilder {
    //     name: Option<String>,
    //     age: Option<u32>,
    //     email: Option<String>,
    // }
    //
    // impl UserBuilder {
    //     fn name(mut self, name: String) -> Self { ... }
    //     fn age(mut self, age: u32) -> Self { ... }
    //     fn email(mut self, email: String) -> Self { ... }
    //     fn build(self) -> Result<User, String> { ... }
    // }
    
    fn main() {
        let user = User::builder()
            .name("Alice".to_string())
            .age(30)
            .build()
            .unwrap();
    }
    

    実装の骨組み(詳細は後の章で):

    // Cargo.toml
    // [lib]
    // proc-macro = true
    //
    // [dependencies]
    // syn = "2.0"
    // quote = "1.0"
    // proc-macro2 = "1.0"
    
    use proc_macro::TokenStream;
    use quote::quote;
    use syn::{parse_macro_input, DeriveInput};
    
    #[proc_macro_derive(Builder)]
    pub fn derive_builder(input: TokenStream) -> TokenStream {
        let input = parse_macro_input!(input as DeriveInput);
        let name = &input.ident;
    
        // ここでコード生成ロジックを実装
        let expanded = quote! {
            impl #name {
                pub fn builder() -> #name Builder {
                    // ...
                }
            }
        };
    
        TokenStream::from(expanded)
    }
    

    19.5.3 属性マクロの例

    目標:関数の実行時間を計測する属性

    // 使用例
    #[measure_time]
    fn heavy_computation() {
        // 重い処理
    }
    
    // これが展開される:
    // fn heavy_computation() {
    //     let _start = std::time::Instant::now();
    //     // 元の処理
    //     println!("実行時間: {:?}", _start.elapsed());
    // }
    

    19.5.4 関数風マクロの例

    目標:SQL風のクエリDSL

    // 使用例
    let users = sql! {
        SELECT name, age FROM users WHERE age > 18
    };
    
    // これがRustコードに変換される
    

    ---

    19.6 実践的なDSL設計

    19.6.1 コンフィグDSL

    目標:設定ファイルを記述するDSL

    macro_rules! config {
        (
            $(
                $section:ident {
                    $(
                        $key:ident = $value:expr
                    ),* $(,)?
                }
            )*
        ) => {
            {
                use std::collections::HashMap;
    
                let mut config = HashMap::new();
    
                $(
                    let mut section = HashMap::new();
                    $(
                        section.insert(
                            stringify!($key).to_string(),
                            $value.to_string()
                        );
                    )*
                    config.insert(stringify!($section).to_string(), section);
                )*
    
                config
            }
        };
    }
    
    fn main() {
        let settings = config! {
            database {
                host = "localhost",
                port = 5432,
                name = "mydb",
            }
            server {
                host = "0.0.0.0",
                port = 8080,
            }
        };
    
        println!("{:#?}", settings);
    }
    

    19.6.2 HTML DSL

    macro_rules! html {
        // 空タグ: <br/>
        ($tag:ident /) => {
            format!("<{}/>", stringify!($tag))
        };
    
        // 内容あり: <p>text</p>
        ($tag:ident { $($content:tt)* }) => {
            format!("<{}>{}</{}>",
                    stringify!($tag),
                    html!($($content)*),
                    stringify!($tag))
        };
    
        // テキストノード
        ($text:expr) => {
            format!("{}", $text)
        };
    
        // 複数要素
        ($($element:tt)*) => {
            {
                let mut result = String::new();
                $(
                    result.push_str(&html!($element));
                )*
                result
            }
        };
    }
    
    fn main() {
        let page = html! {
            html {
                head {
                    title { "My Page" }
                }
                body {
                    h1 { "Welcome" }
                    p { "Hello, world!" }
                    br /
                }
            }
        };
    
        println!("{}", page);
    }
    

    19.6.3 状態機械DSL

    macro_rules! state_machine {
        (
            initial: $initial:ident,
            states: {
                $(
                    $state:ident => {
                        $(
                            $event:ident -> $next:ident
                        ),* $(,)?
                    }
                ),* $(,)?
            }
        ) => {
            #[derive(Debug, Clone, Copy, PartialEq)]
            enum State {
                $($state,)*
            }
    
            #[derive(Debug, Clone, Copy)]
            enum Event {
                $($($event,)*)*
            }
    
            impl State {
                fn transition(self, event: Event) -> Option<State> {
                    match (self, event) {
                        $(
                            $(
                                (State::$state, Event::$event) => Some(State::$next),
                            )*
                        )*
                        _ => None,
                    }
                }
    
                fn initial() -> State {
                    State::$initial
                }
            }
        };
    }
    
    // 使用例:信号機
    state_machine! {
        initial: Red,
        states: {
            Red => {
                TimerExpired -> Green,
            },
            Green => {
                TimerExpired -> Yellow,
            },
            Yellow => {
                TimerExpired -> Red,
            },
        }
    }
    
    fn main() {
        let mut state = State::initial();
        println!("初期状態: {:?}", state);
    
        state = state.transition(Event::TimerExpired).unwrap();
        println!("次の状態: {:?}", state);
    }
    

    ---

    19.7 マクロのベストプラクティス

    19.7.1 マクロを使うべきとき

    使うべき場合

  • ボイラープレートの削減
- 同じパターンのコードを何度も書く場合 - テストケースの生成

  • DSLの実装
- 特定のドメインに特化した記法が必要

  • 可変長引数
- println!, vec!のように任意個の引数が必要

  • コンパイル時計算
- パフォーマンスクリティカルな処理

避けるべき場合

  • 普通の関数で十分
- 型が固定、引数も固定の場合

  • デバッグが困難
- 複雑すぎるマクロはエラーメッセージがわかりにくい

  • IDEサポート
- マクロは補完やジャンプが効きにくい

19.7.2 読みやすいマクロの書き方

// ❌ 悪い例:読みにくい
macro_rules! m {
    ($x:expr,$y:expr) => {$x+$y};
}

// ✅ 良い例:フォーマット、コメント付き
macro_rules! add {
    // 2つの式を加算
    ($x:expr, $y:expr) => {
        $x + $y
    };
}

ドキュメントコメント

/// 2つの値を加算するマクロ
///
/// # Examples
///
/// 
/// let result = add!(2, 3); /// assert_eq!(result, 5); ///
macro_rules! add {
    ($x:expr, $y:expr) => {
        $x + $y
    };
}

19.7.3 エラーハンドリング

macro_rules! safe_divide {
    ($numerator:expr, $denominator:expr) => {
        {
            let denom = $denominator;
            if denom == 0 {
                panic!("0で除算しようとしました");
            }
            $numerator / denom
        }
    };
}

fn main() {
    let result = safe_divide!(10, 2);  // OK: 5
    // let result = safe_divide!(10, 0);  // panic!
}

---

19.8 マクロの高度なテクニック

19.8.1 TTマンチング(Token Tree Munching)

再帰的なマクロ展開

macro_rules! count {
    // ベースケース:要素がない
    () => {
        0
    };

    // 再帰ケース:1つ消費して再帰
    ($head:expr $(, $tail:expr)*) => {
        1 + count!($($tail),*)
    };
}

fn main() {
    let n = count!(1, 2, 3, 4, 5);
    println!("要素数: {}", n);  // 5
}

19.8.2 内部ルール

macro_rules! complex_macro {
    // 公開インターフェース
    (public $x:expr) => {
        complex_macro!(@internal $x, 42)
    };

    // 内部実装(@で始めるのが慣習)
    (@internal $x:expr, $default:expr) => {
        $x + $default
    };
}

19.8.3 マクロのエクスポート

// lib.rs
#[macro_export]
macro_rules! my_macro {
    () => {
        println!("exported macro!");
    };
}

// 別クレートから使用
use my_crate::my_macro;

my_macro!();

---

19.9 まとめ

学んだこと

  • マクロの基礎
- マクロと関数の違い - macro_rules! の構文

  • パターンマッチング
- メタ変数($x:expr, $t:tyなど) - 繰り返し($(...)

  • 実践的なマクロ
- HashMap初期化 - 計測マクロ - テストケース生成

  • 手続き的マクロ
- カスタム derive - 属性マクロ - 関数風マクロ

  • DSL設計
- コンフィグDSL - HTML DSL - 状態機械DSL

次のステップ

次の章では、Rustのテストシステムを詳しく学びます:

  • 単体テスト
  • 統合テスト
  • ドキュメントテスト
  • テスト駆動開発

---

参考資料

公式ドキュメント

追加リソース