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
macro_rules!Basic Syntax and Matching Rules for Declaration Macros- How to Use the Repeat Patterns
$()*and$()+ - Why
vec!andprintln!are macros rather than functions—the fundamental difference between functions and macros Common built-in macros:vec!、println!、format!、todo!、unimplemented! - An Introduction to Macro Hygiene and the Concept of Process Macros
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.
- The logic for each function is almost identical: read the base rate, calculate the distance factor, and add the fuel surcharge
- The only difference is that each shipping method has a different rate schedule and different delivery times.
- Tom copied and pasted it 5 times, tweaked the numbers a bit—looks like he got it done in 10 minutes.
So what happened?
- On Day 3, the rules for calculating fuel costs changed—Tom needed to modify five functions, but he missed one.
- In Week 5, the "cold chain transportation" option was added—Tom copied and pasted it for the sixth time.
- By the second month, the code had ballooned to 300 lines, 200 of which were duplicates.
"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
// 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:
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
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:
Name : <s.name()>
Age : <s.age()>
Grade : <s.grade()>
// ============================================
// 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:
Name : Alice
Age : 20
Grade : A
The macro
create_getter!takes a field name and a type, and expands into a completepub fndefinition. When expanded, three calls tocreate_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:
[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 ===
// ============================================
// 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:
=== 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.,Veccan 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:
=== 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! ===
// ============================================
// 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:
=== 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 amacro_rules!declaration macro! It uses$($x:expr),+to match a comma-separated list of expressions, then expands into repeated blocks ofv.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 correspondingpushcode.
▶ Example 4: Using the built-in macros todo! and unimplemented! (Difficulty: ⭐)
Output:
<log_msg>
(no items in inventory)
#<item.id> <item.name> (qty: <item.quantity>)
=== Inventory Manager ===
Current inventory:
Found: <item>
=== Demo completed ===
// ============================================
// 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:
=== 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!()andunimplemented!()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:
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 ===
// ============================================
// 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:
=== 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
tmpinside the macro and atmpoutside 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 namedtmpand the caller happens to have a variable namedtmp, it can lead to bugs that are difficult to debug.
▶ Example 6: Comprehensive Example—Building a Mini Test Framework with Macros (Difficulty ⭐⭐⭐)
Output:
==============================
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 ===
// ============================================
// 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:
=== 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
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.$()* and $()+ in macro_rules!?$()* 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 $()*.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.todo!() and unimplemented!()?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.#[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
macro_rules!is Rust's declarative macro system, which uses pattern matching to replace a stream of input tokens with code templates that are expanded at compile time.- Repetition Patterns
$()*(zero or more times) and$()+(one or more times) are the core mechanisms for implementing variable-argument macros. - Common built-in macros
vec!,println!,format!,todo!, andunimplemented!are essentially macro declarations;vec!expands toVec::new()followed by a repetition ofpush. - Functions vs. Macros: Functions operate on runtime values, while macros operate on code at compile time; function parameters are fixed, while macro parameters are variable; functions perform type checking, while macros undergo type checking only after expansion.
- Macro hygiene ensures that internal macro variables do not leak into the outer scope, thereby avoiding name conflicts with C language macros.
- Process macros are a more advanced macro system that includes three types: derived macros, attribute macros, and function macros. They must be defined in a separate
proc-macrocrate.
📝 Exercises
- 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 inmainand print the result. - Difficulty ⭐⭐: Write a
assert_equal!macro that takes two expressions as arguments. If they are equal, print✅ PASS; otherwise, print❌ FAIL: left != right. Use thestringify!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. - 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 theDisplaytrait (with each variant displayed as its corresponding string). For example,create_enum_with_display!(Color, Red, Green, Blue)expands into anColorenum, whereRedis displayed as"Red". Hint: Use the$()*repetition pattern andstringify!.