404 Not Found

404 Not Found


nginx

Rustマクロ入門:マクロの宣言とメタプログラミング

マクロはRustの「コード生成マシン」です。マクロを一度記述するだけで、同じコードが無数に自動的に生成されるため、プログラマーはコピー&ペーストという面倒な作業から解放されます。

関数が「繰り返し呼び出しのためのロジックをカプセル化したもの」であるならば、マクロは「繰り返し展開のためのコード生成ルールをカプセル化したもの」と言えます。関数は実行時の値に対して動作するのに対し、マクロはコンパイル時にコードそのものに対して動作します。つまり、関数は工場の組立ライン(原材料を入力し、製品を出力する)であり、マクロは工場の設計図(設計図を入力し、組立ライン全体を出力する)のようなものです。


1. 学習内容


2. 紙幣印刷機の物語

(1) 苦悩:重複コードという泥沼

トムは、ある物流会社でRustエンジニアとして働いています。彼には、一見単純そうなタスクが割り当てられました。それは、5つの輸送手段(トラック、船、飛行機、列車、ドローン)それぞれについて、送料と配送予定時間を計算する関数を記述することでした。

それで、一体何が起きたの?

「ルールを一度記述するだけで、類似する関数をすべて自動的に生成できる方法があれば素晴らしいのですが……」

(2) Rustのマクロによるアプローチ

RUST
// Define using a macro"Shipping Cost Calculator"Template
macro_rules! create_shipping_calculator {
    // Matching Patterns: Name of Mode of Transportation + Rate per kilometer + Speed
    ($name:ident, $rate_per_km:expr, $speed:expr) => {
        fn $name(distance: f64) -> (f64, f64) {
            let base_cost = distance * $rate_per_km;
            let fuel_surcharge = base_cost * 0.1;
            let total = base_cost + fuel_surcharge;
            let time_hours = distance / $speed;
            (total, time_hours)
        }
    };
}

// Single-line macro call = Generate a complete function
create_shipping_calculator!(truck,  1.5,  60.0);
create_shipping_calculator!(ship,   0.8,  30.0);
create_shipping_calculator!(plane,  5.0, 800.0);
create_shipping_calculator!(train,  1.2,  80.0);
create_shipping_calculator!(drone,  2.0,  50.0);

fn main() {
    let distance = 500.0;
    // Every function exists by default.,Just like handwriting
    println!("Truck :  cost=${:.2}, time={:.1}h", truck(distance).0,  truck(distance).1);
    println!("Ship  :  cost=${:.2}, time={:.1}h", ship(distance).0,   ship(distance).1);
    println!("Plane :  cost=${:.2}, time={:.1}h", plane(distance).0,  plane(distance).1);
    println!("Train :  cost=${:.2}, time={:.1}h", train(distance).0,  train(distance).1);
    println!("Drone :  cost=${:.2}, time={:.1}h", drone(distance).0,  drone(distance).1);
}

出力:

TEXT
Truck :  cost=$825.00, time=8.3h
Ship  :  cost=$440.00, time=16.7h
Plane :  cost=$2750.00, time=0.6h
Train :  cost=$660.00, time=6.2h
Drone :  cost=$1100.00, time=10.0h

マクロは「コードプリンター」のようなものです。テンプレート(印刷版)を設計しておけば、マクロを呼び出すたびに、完全なコードが「印刷」されます。変更を加える際は、テンプレートの1か所だけを修正すれば、生成されるすべてのコードが同時に更新されます。各関数を1つずつ手作業で修正する必要はもうありません。


3. 基本概念

(1) マクロ展開プロセス

100%
graph TB
    A[macro_rules! Declaration Macro] --> B[Matching Arm 1: Pattern => Template]
    A --> C[Matching Arm 2: Pattern => Template]
    A --> D[Matching Arm N: Pattern => Template]

    B --> E[Compiler Matching input token]
    C --> E
    D --> E

    E --> F{Match Successful?}
    F -->|Yes| G[Replace the template and expand the code]
    F -->|No| H[Compilation Error: Mismatch]

    G --> I[Generate AST Node]
    I --> J[Continue compiling]

    style A fill:#4a90d9,color:#fff
    style E fill:#e6a23c,color:#fff
    style F fill:#f56c6c,color:#fff
    style G fill:#67c23a,color:#fff

(2) 関数とマクロの比較

次元 関数 (fn) マクロ (macro_rules!)
実行タイミング 実行時呼び出し コンパイル時の展開
パラメータ数 固定 可変(繰り返しパターンを通じて)
パラメータ型 固定型 任意のトークンストリーム
コード生成 コードを生成しない 新しいコードASTを生成する
戻り値 戻り値の型がある 任意のコードスニペットを生成できる
用法 foo(args) 感嘆符付きの foo!(args)
再帰の制限 スタックの深さ マクロの再帰深さ(デフォルト:128レベル)
衛生 自然隔離の範囲 宣言マクロによる部分的な衛生

(3) 一般的な組み込みマクロ

マクロ 関数
println! stdoutに出力し、改行を追加する println!("Hello, {}!", name)
print! 改行なしで標準出力に出力する print!("count: {}", i)
format! フォーマットされた文字列はStringを返す let s = format!("{}:{}", h, m)
vec! ベクトルを素早く作成 let v = vec![1, 2, 3]
todo! プレースホルダー。実行時にパニックを引き起こす fn foo() { todo!() }
unimplemented! 未実装のフラグ、実行時パニック fn bar() { unimplemented!() }
eprintln! stderr に出力 eprintln!("Error: {}", msg)
write! fmt::Write を実装する型 write!(&mut s, "{}", val)
concat! コンパイル時の文字列連結 concat!("a", "b", "c")"abc"
stringify! 式を文字列リテラルに変換する stringify!(1+2)"1 + 2"

(4) 宣言型マクロと手続き型マクロの比較

次元 宣言マクロ macro_rules! プロシージャマクロ (Proc Macro)
定義方法 macro_rules! name { ... } スタンドアロン・クレート + #[proc_macro_*]
展開のタイミング コンパイル時のパターンマッチングと置換 コンパイル時の手続き型コード生成
機能 パターンマッチング + トークン置換 あらゆるASTの読み取り・生成が可能
複雑度 低(宣言型) 高(ASTを処理するためにRustコードの記述が必要)
サブカテゴリ なし 派生マクロ #[derive] / 属性マクロ #[attr] / 機能マクロ name!()
代表的な用途 vec![], println! serde::Serialize, tokio::main
デバッグの難易度

4. マクロの例

(1) ▶ サンプル:最初のマクロの宣言――ゲッター関数の自動生成(難易度 ⭐)

RUST
// ============================================
// Usage macro_rules! Define a Macro
// Scene: Automatically Generate getter Methods for Structure Fields
// ============================================

// Macro Definitions: Generate getter Function based on field name and type
// A macro name followed by ! Indicates that this is a macro
macro_rules! create_getter {
    // Matching Patterns: $name is an identifier (ident), $ty is a type (ty)
    // => The code block below is a template expansion.
    ($name:ident, $ty:ty) => {
        pub fn $name(&self) -> $ty {
            self.$name.clone()
        }
    };
}

// Structures That Use Macros
#[derive(Debug)]
struct Student {
    name: String,
    age: u32,
    grade: String,
}

impl Student {
    // Written by hand getter —— You have to write one for each field.
    // pub fn name(&self) -> String { self.name.clone() }
    // pub fn age(&self) -> u32 { self.age }
    // pub fn grade(&self) -> String { self.grade.clone() }

    // Automatically Generated Using a Macro —— One per line getter
    create_getter!(name, String);
    create_getter!(age, u32);
    create_getter!(grade, String);
}

fn main() {
    let s = Student {
        name: "Alice".to_string(),
        age: 20,
        grade: "A".to_string(),
    };

    // Generated by calling a macro getter Methods
    println!("Name  : {}", s.name());
    println!("Age   : {}", s.age());
    println!("Grade : {}", s.grade());
}

出力:

TEXT
Name  : Alice
Age   : 20
Grade : A

マクロ create_getter! はフィールド名と型を受け取り、完全な pub fn 定義に展開されます。展開されると、create_getter! を 3 回呼び出すことは、3 つのゲッター関数を手作業で記述することと同等になります。これが「コードプリンター」のプロトタイプです。テンプレートがコード生成ルールを定義し、呼び出しごとにコードの一部が出力されます。


(2) ▶ サンプル:繰り返しパターン $()* および $()+ — 可変パラメータマクロ (難易度 ⭐⭐)

RUST
// ============================================
// Repetition Patterns in Presentation Macros:$()* Zero or more times、$()+ Once or multiple times
// Scene: A Mini Test Framework, Supports multiple assertions
// ============================================

// Macro: Run Multiple Test Cases, each case includes a name + Expression + Expected Value
// $()* Indicates that the pattern inside the parentheses can be repeated zero or more times
macro_rules! run_tests {
    // Each test consists of (Name, Expression, Expected Value) Composition of Trios
    // $test_name It is an identifier,$expr It is an expression,$expected It is an expression
    ($( $test_name:ident, $expr:expr, $expected:expr );* $(;)?) => {
        $(
            println!("[Test] {} ...", stringify!($test_name));
            let result = $expr;
            let expected: i32 = $expected;
            if result == expected {
                println!("  ✅ PASS: {} == {}", result, expected);
            } else {
                println!("  ❌ FAIL: {} != {} (expected {})", stringify!($expr), result, expected);
            }
        )*
    };
}

// Macro: Calculate the sum of any number of values
// $()+ Indicates that the pattern inside the parentheses must be repeated at least once
macro_rules! sum_of {
    // Usage $()+ At least one argument is required.
    ($($x:expr),+ $(,)?) => {
        // 0 + $x Cumulative total: 0 + a + b + c ...
        {
            let mut sum = 0i64;
            $(
                sum += $x as i64;
            )+
            sum
        }
    };
}

fn main() {
    println!("=== Mini Test Framework ===");

    // Call run_tests! macro - Pass multiple test cases
    run_tests! {
        add_one,    1 + 1, 2;
        multiply,   3 * 4, 12;
        subtract,   10 - 3, 7;
        power,      2 * 2 * 2, 8
    }

    println!("\n=== Sum Calculator ===");

    // Call sum_of! macro - Pass any number of arguments
    let s1 = sum_of!(1, 2, 3, 4, 5);
    println!("sum_of!(1..5) = {}", s1);

    let s2 = sum_of!(10, 20, 30);
    println!("sum_of!(10,20,30) = {}", s2);

    // A single parameter is also acceptable.
    let s3 = sum_of!(42);
    println!("sum_of!(42) = {}", s3);

    println!("\n=== Done ===");
}

出力:

TEXT
=== Mini Test Framework ===
[Test] add_one ...
  ✅ PASS: 2 == 2
[Test] multiply ...
  ✅ PASS: 12 == 12
[Test] subtract ...
  ✅ PASS: 7 == 7
[Test] power ...
  ✅ PASS: 8 == 8

=== Sum Calculator ===
sum_of!(1..5) = 15
sum_of!(10,20,30) = 60
sum_of!(42) = 42

=== Done ===

$()* および $()+ は、マクロで「可変引数」を実装する上で重要な役割を果たします。$()* は「0回以上の繰り返し」(例:Vec は空でもよい)を示し、$()+ は「1回以上の繰り返し」(少なくとも1つの引数)を示します。繰り返しパターン内では、区切り文字 (,; など) を使用して、パラメータのグループ化方法を制御することもできます。


(3) ▶ サンプル:vec! マクロの簡略化された実装 — 組み込みマクロの仕組みを理解する (難易度 ⭐⭐)

RUST
// ============================================
// Implement a simplified version of the vec! macro
// Understanding vec! The Underlying Principles of Macro Expansion
// ============================================

// Simplified vec! macro - Does not support vec![x; n] syntax
macro_rules! my_vec {
    // Empty vector
    () => {
        Vec::new()
    };
    // A single element
    ($elem:expr) => {
        {
            let mut v = Vec::new();
            v.push($elem);
            v
        }
    };
    // Multiple elements,Separated by commas
    ($($x:expr),+ $(,)?) => {
        {
            let mut v = Vec::new();
            $(
                v.push($x);
            )+
            v
        }
    };
}

fn main() {
    // Use the built-in vec! macro
    let builtin_empty: Vec<i32> = vec![];
    let builtin_one = vec![42];
    let builtin_multi = vec![1, 2, 3, 4, 5];

    println!("=== Built-in vec! ===");
    println!("empty  : {:?}", builtin_empty);
    println!("one    : {:?}", builtin_one);
    println!("multi  : {:?}", builtin_multi);

    // Use Custom my_vec! macro
    let my_empty: Vec<i32> = my_vec![];
    let my_one = my_vec![42];
    let my_multi = my_vec![10, 20, 30, 40, 50];

    println!("\n=== Custom my_vec! ===");
    println!("my_empty : {:?}", my_empty);
    println!("my_one   : {:?}", my_one);
    println!("my_multi : {:?}", my_multi);

    // Verify Functional Consistency
    assert_eq!(builtin_multi.len(), 5);
    assert_eq!(my_multi.len(), 5);
    assert_eq!(builtin_multi, vec![1, 2, 3, 4, 5]);
    assert_eq!(my_multi, vec![10, 20, 30, 40, 50]);

    println!("\n=== All assertions passed! ===");
}

出力:

TEXT
=== Built-in vec! ===
empty  : []
one    : [42]
multi  : [1, 2, 3, 4, 5]

=== Custom my_vec! ===
my_empty : []
my_one   : [42]
my_multi : [10, 20, 30, 40, 50]

=== All assertions passed! ===

表示されている vec! マクロは、実は macro_rules! という宣言マクロなのです!これは $($x:expr),+ を使用してカンマ区切りの式リストに一致させ、その後、v.push($x) コードの繰り返しブロックに展開されます。これは「関数ではできないこと」です— vec![1, 2, 3] もし関数として記述した場合、コンパイル時に要素数を特定し、対応する push コードを生成することは不可能です。


(4) ▶ サンプル:組み込みマクロ todo! および unimplemented! の使用(難易度:⭐)

RUST
// ============================================
// Demonstrate Built-in Macros: todo! / unimplemented! / format! / eprintln!
// Scene: An inventory management system currently under development
// ============================================

// Simulated Inventory Items
#[derive(Debug)]
struct InventoryItem {
    id: u32,
    name: String,
    quantity: u32,
}

// Inventory Manager
struct InventoryManager {
    items: Vec<InventoryItem>,
}

impl InventoryManager {
    fn new() -> InventoryManager {
        InventoryManager {
            items: Vec::new(),
        }
    }

    // Implemented: Add Item
    fn add_item(&mut self, id: u32, name: &str, quantity: u32) {
        self.items.push(InventoryItem {
            id,
            name: name.to_string(),
            quantity,
        });
        // Usage format! Macro-Formatted Log Messages
        let log_msg = format!("[INFO] Added item: {} (id={}, qty={})", name, id, quantity);
        println!("{}", log_msg);
    }

    // Implemented: Search for Products
    fn find_item(&self, id: u32) -> Option<&InventoryItem> {
        self.items.iter().find(|item| item.id == id)
    }

    // Not implemented: Update Inventory
    fn update_quantity(&mut self, _id: u32, _new_qty: u32) {
        // TODO: Implement the inventory update logic
        // todo!() will panic with a "Not implemented" message
        todo!("update_quantity: id={} quantity={}", _id, _new_qty);
    }

    // Not implemented: Generate an Inventory Report
    fn generate_report(&self) -> String {
        // unimplemented!() Indicates that this feature has not been implemented yet
        unimplemented!("generate_report() is not yet implemented");
    }

    // Implemented: Print All Items
    fn list_items(&self) {
        if self.items.is_empty() {
            println!("  (no items in inventory)");
            return;
        }
        for item in &self.items {
            println!("  #{} {} (qty: {})", item.id, item.name, item.quantity);
        }
    }
}

fn main() {
    let mut manager = InventoryManager::new();

    println!("=== Inventory Manager ===");

    // Add Item
    manager.add_item(101, "Laptop", 10);
    manager.add_item(102, "Mouse", 50);
    manager.add_item(103, "Keyboard", 30);

    // List Products
    println!("\nCurrent inventory:");
    manager.list_items();

    // Search for Products
    if let Some(item) = manager.find_item(102) {
        println!("\nFound: {:?}", item);
    }

    // Uncommenting the following code will panic (But it won't result in a compilation error):
    // manager.update_quantity(101, 8);   // panics: "not yet implemented"
    // let report = manager.generate_report(); // panics: "not yet implemented"

    println!("\n=== Demo completed ===");
    println!("Note: Try uncommenting update_quantity() or generate_report() to see todo!/unimplemented! in action.");
}

出力:

TEXT
=== Inventory Manager ===
[INFO] Added item: Laptop (id=101, qty=10)
[INFO] Added item: Mouse (id=102, qty=50)
[INFO] Added item: Keyboard (id=103, qty=30)

Current inventory:
  #101 Laptop (qty: 10)
  #102 Mouse (qty: 50)
  #103 Keyboard (qty: 30)

Found: InventoryItem { id: 102, name: "Mouse", quantity: 50 }

=== Demo completed ===
Note: Try uncommenting update_quantity() or generate_report() to see todo!/unimplemented! in action.

todo!()unimplemented!() は、Rust 開発者にとって「強力なプレースホルダーツール」です。todo!() には説明文 (todo!("msg: {}", val)) を追加できるため、開発中の未完成な機能をマークするのに最適です。unimplemented!()は、インターフェース定義内でまだ実装されていないメソッドをマークするのに適しています。どちらも実行時にパニックを引き起こしますが、コンパイルエラーにはなりません。これにより、まずコードを書き、段階的に実装していくことが可能になります。


(5) ▶ サンプル:マクロの衛生管理 — 内部のマクロ変数が外部環境を汚染しない(難易度 ⭐⭐⭐)

RUST
// ============================================
// Demonstrating Macro Hygiene
// Variables created within a macro do not conflict with those in the outer scope.
// ============================================

// Note: Rust declaration macros have "partial hygiene"
// For internal use by Hong $ Captured variable names will not conflict with external ones
// However, identifiers written directly within the macro are in Rust 2018+ There are special rules in this case

// Macro: Create a local variable tmp and swap values
// Note: tmp written directly within the macro is's hygienic.——It will not affect the outside world. tmp
macro_rules! swap_with_tmp {
    ($a:expr, $b:expr) => {
        {
            let tmp = $a;
            $a = $b;
            $b = tmp;
        }
    };
}

// Macro: Demonstrate Hygiene - Even if there is a tmp variable outside
macro_rules! demonstrate_hygiene {
    ($x:expr) => {
        {
            // Defined internally by the macro tmp It's hygienic.
            let tmp = $x * 2;
            println!("  Inside macro: tmp = {}", tmp);
            tmp
        }
    };
}

// Counterexample of Unhygienic Conditions (Demonstrated differently)
// Note: Rust declarative macros do not allow creating variables that cause cross-scope conflicts.
// So here we use concat Simulation"Non-health-related"Potential Issues

fn main() {
    println!("=== Macro Hygiene Demonstration ===\n");

    // Scenario 1: Internal variables in a macro do not affect external variables
    println!("1. Macro internal variable vs external variable:");
    let mut x = 10;
    let mut y = 20;

    println!("  Before swap: x={}, y={}", x, y);

    // The macro uses the following internally: tmp,But the external variable names tmp Not affected
    swap_with_tmp!(x, y);

    println!("  After swap:  x={}, y={}", x, y);

    // External tmp The variable does not exist. —— Inside the macro tmp It's hygienic.
    // If you uncomment the following line, you'll get a compilation error.:
    // println!("tmp from macro = {}", tmp);  // ❌ Compilation Error: tmp not found

    // Scenario 2: Internal macro variables do not conflict with external variables of the same name
    println!("\n2. Hygiene with same name:");
    let tmp = 100;  // External tmp
    println!("  Outside macro: tmp = {}", tmp);

    let result = demonstrate_hygiene!(5);
    println!("  Return value: {}", result);
    println!("  Outside macro again: tmp = {}", tmp);  // It's still 100,Not affected by macros

    // Scenario 3: Why Is Hygiene Important?
    println!("\n3. Why hygiene matters:");
    println!("  Without hygiene, macros could accidentally:");
    println!("  - Overwrite variables in the caller's scope");
    println!("  - Create hard-to-find bugs");
    println!("  - Break encapsulation of the calling code");
    println!("  Rust's hygiene prevents these issues at compile time.");

    println!("\n=== Demo completed ===");
}

出力:

TEXT
=== Macro Hygiene Demonstration ===

1. Macro internal variable vs external variable:
  Before swap: x=10, y=20
  After swap:  x=20, y=10

2. Hygiene with same name:
  Outside macro: tmp = 100
  Inside macro: tmp = 10
  Return value: 10
  Outside macro again: tmp = 100

3. Why hygiene matters:
  Without hygiene, macros could accidentally:
  - Overwrite variables in the caller's scope
  - Create hard-to-find bugs
  - Break encapsulation of the calling code
  Rust's hygiene prevents these issues at compile time.

マクロの衛生性は、Rustのマクロにおける重要な特徴の一つです。マクロ内で作成された変数名は、呼び出し元のスコープに「漏れ出」ることはありません。上記の例では、マクロ内にtmpがあり、マクロの外側にtmpがありますが、これらは互いに干渉しません。これにより、Cのマクロでよく見られる「名前衝突」の問題を回避できます。Cでは、マクロがtmpという名前の変数を使用していて、呼び出し元にたまたまtmpという名前の変数があると、デバッグが困難なバグにつながる可能性があります。


(6) ▶ サンプル:総合的な例――マクロを使ったミニテストフレームワークの構築(難易度 ⭐⭐⭐)

RUST
// ============================================
// Comprehensive Example: Building a Mini Unit Testing Framework Using Macros
// Use in combination:Matching Patterns、Repetition Pattern、Built-in Macros
// ============================================

// Test Results Summary
struct TestStats {
    total: u32,
    passed: u32,
    failed: u32,
}

impl TestStats {
    fn new() -> TestStats {
        TestStats { total: 0, passed: 0, failed: 0 }
    }

    fn print_summary(&self) {
        println!("\n==============================");
        println!("Test Summary:");
        println!("  Total : {}", self.total);
        println!("  Passed: {}", self.passed);
        println!("  Failed: {}", self.failed);
        if self.failed == 0 {
            println!("  ✅ All tests passed!");
        } else {
            println!("  ❌ {} test(s) failed", self.failed);
        }
        println!("==============================");
    }
}

// Macro: Define a set of test cases
// Each test is identified by its name、Assertion Expressions、Expected Results Breakdown
macro_rules! test_suite {
    // Matches zero or more tests
    ($( $name:ident: $left:expr, $op:tt, $right:expr );* $(;)?) => {{
        let mut stats = TestStats::new();
        $(
            stats.total += 1;
            print!("[Test] {}: {} {} {} ... ", stringify!($name),
                stringify!($left), stringify!($op), stringify!($right));

            let passed = match $op {
                == => { $left == $right }
                != => { $left != $right }
                <  => { $left < $right }
                <= => { $left <= $right }
                >  => { $left > $right }
                >= => { $left >= $right }
                _ => { panic!("Unsupported operator: {}", stringify!($op)); }
            };

            if passed {
                stats.passed += 1;
                println!("✅ PASS");
            } else {
                stats.failed += 1;
                println!("❌ FAIL (got {:?}, expected {:?})", $left, $right);
            }
        )*
        stats
    }};
}

fn main() {
    println!("=== Mini Test Framework ===");
    println!("Using macro-generated test suite\n");

    // Test Suites Defined Using Macros
    let stats = test_suite! {
        test_add:  2 + 2, ==, 4;
        test_sub:  10 - 3, ==, 7;
        test_mul:  3 * 4, ==, 12;
        test_div:  10 / 2, ==, 5;
        test_gt:   100, >, 50;
        test_lt:   3, <, 10;
        test_eq:   "hello", ==, "hello";
        test_neq:  42, !=, 0
    };

    // Print Statistics
    stats.print_summary();

    // Verify that all tests have passed
    assert_eq!(stats.total, 8);
    assert_eq!(stats.passed, 8);
    assert_eq!(stats.failed, 0);

    println!("\n=== Demo completed ===");
}

出力:

TEXT
=== Mini Test Framework ===
Using macro-generated test suite

[Test] test_add: 2 + 2 == 4 ... ✅ PASS
[Test] test_sub: 10 - 3 == 7 ... ✅ PASS
[Test] test_mul: 3 * 4 == 12 ... ✅ PASS
[Test] test_div: 10 / 2 == 5 ... ✅ PASS
[Test] test_gt: 100 > 50 ... ✅ PASS
[Test] test_lt: 3 < 10 ... ✅ PASS
[Test] test_eq: "hello" == "hello" ... ✅ PASS
[Test] test_neq: 42 != 0 ... ✅ PASS

==============================
Test Summary:
  Total : 8
  Passed: 8
  Failed: 0
  ✅ All tests passed!
==============================

=== Demo completed ===

この包括的な例は、マクロの真の威力を示しています。test_suite! マクロは、一連のテスト定義(名前、式、比較演算子、期待値)を受け取り、それらを完全なテスト実行コードに自動的に展開します。このマクロを使用して記述された8つのテストケースを呼び出すのに必要なコードはわずか8行ですが、同等のコードを手作業で記述すると、少なくとも60行は必要になります。さらに重要なのは、「テストのタイムアウト」や「テストのグループ化」といった機能を追加する必要がある場合でも、マクロテンプレートの1箇所を変更するだけで、すべてのテストが自動的に更新されるという点です。


❓ よくある質問

Q マクロと関数の具体的な違いは何ですか? 関数の代わりにマクロを使うべき場合はいつですか?
A マクロはコンパイル時にコードを展開するのに対し、関数は実行時に実行されます。 マクロには、関数ではできないことがいくつかあります。たとえば、可変個の引数(vec![1, 2, 3]など)の処理、コンパイル時のコード生成、コードスニペットを引数として受け取るなどです。しかし、マクロはデバッグが難しく、コードの可読性が低下し、わかりにくいコンパイル時のエラーメッセージが発生する可能性があります。基本的には関数の使用を優先し、関数ではできない処理が必要な場合にのみマクロを使用してください。
Q macro_rules! における $()*$()+ の違いは何ですか?
A $()* は 0 回以上の繰り返し(空文字列を含む)に一致するのに対し、$()+ は 1 回以上の繰り返し(少なくとも 1 回)に一致します。 たとえば、vec![](空のベクトル)では $()* を使用しますが、vec![1, 2] では $()+ または $()* のいずれかを使用できます。マクロに少なくとも1つの引数を受け入れさせたい場合は $()+ を、引数を0個でも許可する場合は $()* を使用してください。
Q マクロの衛生性とは何ですか?なぜ重要なのでしょうか?
A 衛生性とは、マクロ内で作成された変数が、外側のスコープにある変数と競合しないことを指します。 C言語では、マクロは文字列置換であるため、名前衝突が容易に発生します。例えば、マクロが内部でtmpを使用しているのに、呼び出し元にもたまたまtmpという名前の変数がある場合などです。Rustの宣言マクロは「部分的に衛生的な」仕様となっており、マクロ内で$によってキャプチャされた変数名は外部に漏れないため、このようなバグを防ぐことができます。
Q todo!()unimplemented!() の違いは何ですか?
A どちらも実行時にパニックを引き起こしますが、その意味合いは異なります。todo!() は「この機能は計画されているが、まだ実装されていない」ことを示し、進捗状況を指定するためのパラメータを含めることができます(例:todo!("implement pagination"))。unimplemented!()は「このインターフェース/メソッドは現在、実装予定がない」ことを示します。todo!()は開発中にToDoリストの項目をマークするために、unimplemented!()はトレイトのデフォルト実装で、それぞれより一般的に使用されます。
Q 手続き型マクロとは何ですか?
A 手続き型マクロは、宣言型マクロよりも強力なマクロシステムです。「パターンマッチングと置換」を行うのではなく、「コードを受け取り → Rust関数を実行 → 新しいコードを出力する」という動作をします。 プロシージャルマクロには、#[derive(...)] 派生マクロ(#[derive(Debug)] など)、属性マクロ(#[test] など)、関数マクロ(#[async] など。これは async fn の基盤となっている)の3種類があります。手続き型マクロは別の proc-macro クレートで定義する必要がありますが、macro_rules! 宣言型マクロはどこでも定義できます。このレッスンでは概念のみを扱います。手続き型マクロの詳細な使用法は上級者向けの内容です。

📖 まとめ


📝 練習問題

  1. 難易度 ⭐: 2つの式を引数として受け取り、タプル (expr1, expr2) を返す make_pair! マクロを作成してください。例えば、make_pair!(42, "hello")(42, "hello") に展開されます。main 内でこのマクロを呼び出し、結果を出力してください。
  2. 難易度 ⭐⭐: 2つの式を引数として受け取る assert_equal! マクロを作成してください。それらが等しい場合は ✅ PASS を出力し、そうでない場合は ❌ FAIL: left != right を出力してください。元の式を出力するには stringify! マクロを使用してください。少なくとも3つのテストケース(式が等しい場合と等しくない場合を含む)を作成し、実行してください。
  3. 難易度 ⭐⭐⭐: 列挙型の名前と一連のバリアント名を入力として受け取り、create_enum_with_display! マクロが Display トレイトの列挙型定義と実装を自動的に生成するようにします(各バリアントは対応する文字列として表示されます)。たとえば、create_enum_with_display!(Color, Red, Green, Blue)Color 列挙型に展開され、そこで Red"Red" として表示されます。ヒント:$()* 反復パターンと stringify! を使用してください。
Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%