Rust: Introduction to Rust Macros

Last updated: 2026-08-26

Macros are Rust's "code-generating machines"—write a macro once, and it automatically generates countless instances of the same code, freeing programmers from the drudgery of copy-and-paste.

If a function is "encapsulating logic for repeated invocation," then a macro is "encapsulating code-generation rules for repeated expansion." Functions operate on runtime values, while macros operate on the code itself at compile time. It's like this: a function is a factory assembly line (inputting raw materials, outputting products), while a macro is a factory blueprint (inputting design drawings, outputting the entire assembly line).


1. What You'll Learn



2. The Story of the Money-Printing Machine

(1) The Agony: The Quagmire of Duplicate Code

Tom is a Rust engineer at a logistics company. He was assigned what seemed like a simple task: to write a function for each of the five modes of transportation (truck, ship, plane, train, and drone) to calculate shipping costs and estimated delivery times.

So what happened?

"It would be great if there were a way to write a rule once and have it automatically generate all similar functions..."

(2) The Rust Macro Approach

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);
}

Output:

TEXT 📖 Display only
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

Macros are like "code printers": you design a template (the printing plate), and every time you call the macro, it "prints" a complete piece of code. When you make changes, you only need to modify one place in the template, and all the generated code is updated simultaneously—no more manually modifying each function one by one.



3. Core Concepts

(1) The Macro Expansion Process

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) Comparison of Functions vs. Macros

Dimension Function (fn) Macro (macro_rules!)
Execution Timing Runtime Call Compile-Time Expansion
Number of parameters Fixed Variable (via repetition patterns)
Parameter Type Fixed Type Arbitrary Token Stream
Code Generation Does not generate code Generates a new code AST
Return Value Has a return type Can generate any code snippet
Usage foo(args) foo!(args) with an exclamation mark
Recursion Limits Stack Depth Macro Recursion Depth (default: 128 levels)
Hygiene Natural Isolation Scope Declaration Macro Partial Hygiene

(3) Common Built-in Macros

Macro Function Example
println! Print to stdout and add a newline println!("Hello, {}!", name)
print! Print to stdout without a newline print!("count: {}", i)
format! Formatted string returns a String let s = format!("{}:{}", h, m)
vec! Quickly Create Vec let v = vec![1, 2, 3]
todo! Placeholder, causes a panic at runtime fn foo() { todo!() }
unimplemented! Unimplemented flag, runtime panic fn bar() { unimplemented!() }
eprintln! Print to stderr eprintln!("Error: {}", msg)
write! Types that implement fmt::Write write!(&mut s, "{}", val)
concat! String concatenation at compile time concat!("a", "b", "c")"abc"
stringify! Convert an expression to a string literal stringify!(1+2)"1 + 2"

(4) Comparison of Declaration Macros vs. Procedural Macros

Dimension Declaration Macro macro_rules! Procedure Macro (Proc Macro)
Definition Method macro_rules! name { ... } Standalone crate + #[proc_macro_*]
Expansion Timing Compile-time pattern matching + substitution Compile-time procedural code generation
Capabilities Pattern matching + token replacement Can read/generate any AST
Complexity Low (declarative) High (requires writing Rust code to process the AST)
Subcategory None Derived Macros #[derive] / Attribute Macros #[attr] / Functional Macros name!()
Typical applications vec![], println! serde::Serialize, tokio::main
Debugging Difficulty Moderate High


4. Macro Examples

▶ Example 1: Declaring Your First Macro—Automatically Generating Getter Functions (Difficulty ⭐)

Output:

TEXT 📖 Display only
Name  : <s.name()>
Age   : <s.age()>
Grade : <s.grade()>
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());
}

Output:

TEXT 📖 Display only
Name  : Alice
Age   : 20
Grade : A

The macro create_getter! takes a field name and a type, and expands into a complete pub fn definition. When expanded, three calls to create_getter! are equivalent to writing three getter functions by hand. This is the prototype of the "code printer"—the template defines the code generation rules, and each call prints out a piece of code.


▶ Example 2: Repetition Patterns $()* and $()+—Variable-Parameter Macros (Difficulty ⭐⭐)

Output:

TEXT 📖 Display only
[Test] <stringify!($test_name)> ...
  ✅ PASS: <result> == <expected>
  ❌ FAIL: <stringify!($expr)> != <result> (expected <expected>)
=== Mini Test Framework ===

=== Sum Calculator ===
sum_of!(1..5) = <s1>
sum_of!(10,20,30) = <s2>
sum_of!(42) = <s3>

=== Done ===
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 ===");
}

Output:

TEXT 📖 Display only
=== 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 ===

$()* and $()+ are key to implementing "variable arguments" in macros. $()* indicates "zero or more repetitions" (e.g., Vec can be empty), while $()+ indicates "one or more repetitions" (at least one argument). Within repetition patterns, delimiters (,, ;, etc.) can also be used to control how parameters are grouped.


▶ Example 3: Simplified Implementation of the vec! Macro—Understanding How Built-in Macros Work (Difficulty ⭐⭐)

Output:

TEXT 📖 Display only
=== Built-in vec! ===
empty  : <builtin_empty>
one    : [42]
multi  : [1, 2, 3, 4, 5]

=== Custom my_vec! ===
my_empty : <my_empty>
my_one   : <my_one>
my_multi : <my_multi>

=== All assertions passed! ===
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! ===");
}

Output:

TEXT 📖 Display only
=== 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! ===

The vec! macro you see is actually a macro_rules! declaration macro! It uses $($x:expr),+ to match a comma-separated list of expressions, then expands into repeated blocks of v.push($x) code. This is "something a function cannot do"— vec![1, 2, 3] If written as a function, it would be impossible to determine the number of elements at compile time and generate the corresponding push code.


▶ Example 4: Using the built-in macros todo! and unimplemented! (Difficulty: ⭐)

Output:

TEXT 📖 Display only
<log_msg>
  (no items in inventory)
  #<item.id> <item.name> (qty: <item.quantity>)
=== Inventory Manager ===

Current inventory:

Found: <item>

=== Demo completed ===
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.");
}

Output:

TEXT 📖 Display only
=== 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!() and unimplemented!() are "powerful placeholder tools" for Rust developers. todo!() can include a description (todo!("msg: {}", val)), making it ideal for marking unfinished features during development. unimplemented!() is better suited for marking methods in interface definitions that are not yet implemented. Both will cause a panic at runtime but won't result in compilation errors—allowing you to write the code first and implement it step by step.


▶ Example 5: Macro Hygiene — Internal Macro Variables Do Not Contaminate the External Environment (Difficulty ⭐⭐⭐)

Output:

TEXT 📖 Display only
  Inside macro: tmp = 100
=== Macro Hygiene Demonstration ===

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

2. Hygiene with same name:
  Outside macro: tmp = 100
  Return value: <result>
  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.

=== Demo completed ===
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 ===");
}

Output:

TEXT 📖 Display only
=== 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.

Macro hygiene is an important feature of Rust macros: variable names created within a macro do not "leak" into the caller's scope. In the example above, there is a tmp inside the macro and a tmp outside it, but they do not interfere with each other. This avoids the "name conflict" issues common in C macros—in C, if a macro uses a variable named tmp and the caller happens to have a variable named tmp, it can lead to bugs that are difficult to debug.


▶ Example 6: Comprehensive Example—Building a Mini Test Framework with Macros (Difficulty ⭐⭐⭐)

Output:

TEXT 📖 Display only

==============================
Test Summary:
  Total : <self.total>
  Passed: <self.passed>
  Failed: <self.failed>
  ✅ All tests passed!
  ❌ <self.failed> test(s) failed
==============================
[Test] <stringify!($name)>: <stringify!($left)> <stringify!($op)> <stringify!($right)> ... 
✅ PASS
❌ FAIL (got <$left>, expected <$right>)
=== Mini Test Framework ===
Using macro-generated test suite


=== Demo completed ===
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 ===");
}

Output:

TEXT 📖 Display only
=== 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 ===

This comprehensive example demonstrates the true power of macros: The test_suite! macro takes a set of test definitions (name, expression, comparison operator, expected value) and automatically expands them into complete test execution code. Eight test cases written using the macro require only eight lines of code to call, whereas writing the equivalent code by hand would require at least 60 lines. More importantly, if you need to add features such as "test timeouts" or "test grouping," you only need to modify one place in the macro template, and all tests are automatically updated.


❓ FAQ

Q What exactly is the difference between macros and functions? When should you use a macro instead of a function?
A Macros expand their code at compile time, while functions execute at runtime. Macros can do things that functions cannot: handle a variable number of arguments (such as vec![1, 2, 3]), generate code at compile time, and accept code snippets as arguments. However, macros are harder to debug, result in less readable code, and can lead to obscure compile-time error messages. Prioritize using functions, and use macros only when you need to do something that functions cannot.
Q What is the difference between $()* and $()+ in macro_rules!?
A $()* matches zero or more repetitions (including an empty string), while $()+ matches one or more repetitions (at least one). For example, vec![] (empty vector) uses $()*, while vec![1, 2] can use either $()+ or $()*. If you want the macro to accept at least one argument, use $()+; if you allow zero arguments, use $()*.
Q What is macro hygiene? Why is it important?
A Hygiene refers to the fact that variables created within a macro do not conflict with variables in the outer scope. In C, macros are text substitutions, so name conflicts can easily arise—for example, if a macro uses tmp internally, but the caller also happens to have a variable named tmp. Rust's declaration macros are "partially hygienic": variable names captured by $ within the macro do not leak, thereby preventing such bugs.
Q What is the difference between todo!() and unimplemented!()?
A Both cause a panic at runtime, but their semantics differ. todo!() indicates "this feature is planned but not yet implemented" and can include parameters to specify progress (e.g., todo!("implement pagination")). unimplemented!() indicates "this interface/method is not currently planned for implementation." todo!() is more commonly used during development to mark items on the to-do list, while unimplemented!() is more commonly used in default implementations of traits.
Q What is a procedural macro?
A A procedural macro is a more powerful macro system than declarative macros; it does not perform "pattern matching and substitution," but rather "receives code → executes a Rust function → outputs new code." There are three types: #[derive(...)] derived macros (such as #[derive(Debug)]), attribute macros (such as #[test]), and function macros (such as #[async], which is behind async fn). Procedural macros must be defined in a separate proc-macro crate, while macro_rules! declaration macros can be defined anywhere. This lesson covers only the concepts; detailed usage of procedural macros is advanced material.

📖 Summary


📝 Exercises

  1. Difficulty ⭐: Write a make_pair! macro that takes two expressions as arguments and returns a tuple (expr1, expr2). For example, make_pair!(42, "hello") expands to (42, "hello"). Call it in main and print the result.
  2. Difficulty ⭐⭐: Write a assert_equal! macro that takes two expressions as arguments. If they are equal, print ✅ PASS; otherwise, print ❌ FAIL: left != right. Use the stringify! macro to print the original expressions. Create at least 3 test cases (including cases where the expressions are equal and cases where they are not) and run them.
  3. Difficulty ⭐⭐⭐: Write a create_enum_with_display! macro that takes an enum name and a set of variant names as input, and automatically generates the enum definition and implementation of the Display trait (with each variant displayed as its corresponding string). For example, create_enum_with_display!(Color, Red, Green, Blue) expands into an Color enum, where Red is displayed as "Red". Hint: Use the $()* repetition pattern and stringify!.
Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏