Rust: Rust 宏(Macros)入门:声明宏与元编程
最后更新:2026-08-26
宏是 Rust 的"代码印钞机"——写一次宏,自动生成无数重复代码,让程序员从复制粘贴的苦海中解脱。
如果说函数是"把逻辑封装起来重复调用",那宏就是"把代码生成规则封装起来重复展开"。函数操作的是运行时值,宏操作的是编译期代码本身。这就像:函数是工厂流水线(输入原料,输出产品),宏是工厂建设图纸(输入设计图,输出整条流水线)。
1. 你将学到
macro_rules!声明宏的基本语法与匹配规则- 重复模式
$()*和$()+的用法 - 为什么
vec!和println!是宏而不是函数——函数与宏的本质区别 - 常见内置宏:
vec!、println!、format!、todo!、unimplemented! - 宏的卫生性(hygiene)与过程宏概念入门
2. 印钞机的故事
(1) 痛苦:重复代码的泥潭
Tom 是一家物流公司的 Rust 工程师。他被分配了一个看似简单的任务:为 5 种运输方式(卡车、轮船、飞机、火车、无人机)各写一个计算运费和预估时间的函数。
- 每个函数的逻辑几乎一样:读取基础费率、计算距离系数、附加燃油费
- 唯一的区别是:每种运输方式的费率表不同、速度不同
- Tom 复制粘贴了 5 次,改了改数字——看起来 10 分钟搞定
结果呢?
- 第 3 天,燃油费计算规则变了——Tom 需要改 5 个函数,漏改了 1 个
- 第 5 周,新增了"冷链运输"方式——Tom 又复制粘贴了第 6 次
- 第 2 个月,代码量膨胀到 300 行,其中 200 行是重复的
"如果有个办法写一次规则,自动生成所有类似函数就好了……"
(2) Rust 宏的方案
// 用宏来定义"运费计算器"的模板
macro_rules! create_shipping_calculator {
// 匹配模式:运输方式名称 + 每公里费率 + 速度
($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)
}
};
}
// 一行宏调用 = 生成一个完整函数
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;
// 每个函数都自动存在,就像手写的一样
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);
}
输出:
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
宏就像"代码印钞机":你设计好模板(印钞版),然后每次调用宏就"印刷"出一份完整的代码。修改时只需要改模板一处,所有生成的代码同步更新——再也不用逐个函数手动修改了。
3. 核心概念
(1) 声明宏展开过程
graph TB
A[macro_rules! 声明宏] --> B[匹配臂 1: 模式 => 模板]
A --> C[匹配臂 2: 模式 => 模板]
A --> D[匹配臂 N: 模式 => 模板]
B --> E[编译器匹配 input token]
C --> E
D --> E
E --> F{匹配成功?}
F -->|Yes| G[替换模板并展开代码]
F -->|No| H[编译错误: 不匹配]
G --> I[生成 AST 节点]
I --> J[继续编译]
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) 函数 vs 宏对比
| 维度 | 函数 (fn) | 宏 (macro_rules!) |
|---|---|---|
| 执行时机 | 运行时调用 | 编译期展开 |
| 参数数量 | 固定 | 可变(通过重复模式) |
| 参数类型 | 固定类型 | 任意 token 流 |
| 代码生成 | 不生成代码 | 生成新的代码 AST |
| 返回值 | 有返回值类型 | 可生成任意代码片段 |
| 调用方式 | foo(args) |
foo!(args) 带感叹号 |
| 递归限制 | 栈深度 | 宏递归深度(默认 128 层) |
| 卫生性 | 天然隔离作用域 | 声明宏部分卫生 |
(3) 常见内置宏
| 宏 | 作用 | 示例 |
|---|---|---|
println! |
打印到 stdout 并换行 | println!("Hello, {}!", name) |
print! |
打印到 stdout 不换行 | print!("count: {}", i) |
format! |
格式化字符串返回 String | let s = format!("{}点{}分", h, m) |
vec! |
快速创建 Vec | let v = vec![1, 2, 3] |
todo! |
占位标记,运行时 panic | fn foo() { todo!() } |
unimplemented! |
未实现标记,运行时 panic | 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) 声明宏 vs 过程宏对比
| 维度 | 声明宏 macro_rules! |
过程宏(Proc Macro) |
|---|---|---|
| 定义方式 | macro_rules! name { ... } |
独立 crate + #[proc_macro_*] |
| 展开时机 | 编译期模式匹配 + 替换 | 编译期过程式代码生成 |
| 能力 | 模式匹配 + token 替换 | 可读取/生成任意 AST |
| 复杂度 | 低(声明式) | 高(需写 Rust 代码处理 AST) |
| 子分类 | 无 | 派生宏 #[derive] / 属性宏 #[attr] / 函数式宏 name!() |
| 典型用途 | vec![]、println! |
serde::Serialize、tokio::main |
| 调试难度 | 中等 | 较高 |
4. 宏示例
▶ 示例 1:第一个声明宏——自动生成 getter 函数(难度 ⭐)
// ============================================
// 使用 macro_rules! 定义宏
// 场景:为结构体字段自动生成 getter 方法
// ============================================
// 宏定义:根据字段名和类型生成 getter 函数
// 宏名后跟 ! 表示这是一个宏
macro_rules! create_getter {
// 匹配模式:$name 是标识符(ident),$ty 是类型(ty)
// => 后面的代码块是展开模板
($name:ident, $ty:ty) => {
pub fn $name(&self) -> $ty {
self.$name.clone()
}
};
}
// 使用宏的结构体
#[derive(Debug)]
struct Student {
name: String,
age: u32,
grade: String,
}
impl Student {
// 手动写的 getter —— 一个字段就要写一个
// pub fn name(&self) -> String { self.name.clone() }
// pub fn age(&self) -> u32 { self.age }
// pub fn grade(&self) -> String { self.grade.clone() }
// 用宏自动生成 —— 一行一个 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(),
};
// 调用宏生成的 getter 方法
println!("Name : {}", s.name());
println!("Age : {}", s.age());
println!("Grade : {}", s.grade());
}
输出:
Name : Alice
Age : 20
Grade : A
宏
create_getter!接收一个字段名和一个类型,展开成一个完整的pub fn定义。三个create_getter!调用展开后等价于手写三个 getter 函数。这就是"代码印钞机"的雏形——模板定义了代码生成规则,每次调用印出一份代码。
▶ 示例 2:重复模式 $()* 与 $()+——可变参数宏(难度 ⭐⭐)
// ============================================
// 演示宏的重复模式:$()* 零次或多次、$()+ 一次或多次
// 场景:一个迷你测试框架,支持多个断言
// ============================================
// 宏:运行多个测试用例,每个用例包含名称 + 表达式 + 期望值
// $()* 表示括号内的模式可以重复零次或多次
macro_rules! run_tests {
// 每个测试由 (名称, 表达式, 期望值) 三元组组成
// $test_name 是标识符,$expr 是表达式,$expected 是表达式
($( $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_rules! sum_of {
// 使用 $()+ 要求至少一个参数
($($x:expr),+ $(,)?) => {
// 0 + $x 的累加: 0 + a + b + c ...
{
let mut sum = 0i64;
$(
sum += $x as i64;
)+
sum
}
};
}
fn main() {
println!("=== Mini Test Framework ===");
// 调用 run_tests! 宏 —— 传入多个测试用例
run_tests! {
add_one, 1 + 1, 2;
multiply, 3 * 4, 12;
subtract, 10 - 3, 7;
power, 2 * 2 * 2, 8
}
println!("\n=== Sum Calculator ===");
// 调用 sum_of! 宏 —— 传入任意数量参数
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);
// 单个参数也可以
let s3 = sum_of!(42);
println!("sum_of!(42) = {}", s3);
println!("\n=== Done ===");
}
输出:
=== 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 ===
$()*和$()+是宏实现"可变参数"的关键。$()*表示"零次或多次重复"(像Vec可以为空),$()+表示"一次或多次重复"(至少有一个参数)。重复模式内部还可以用分隔符(,;等)来控制参数的分组方式。
▶ 示例 3:vec! 宏的简化实现——理解内置宏原理(难度 ⭐⭐)
// ============================================
// 模拟实现一个简化版 vec! 宏
// 理解 vec! 宏的底层展开原理
// ============================================
// 简化版 vec! 宏 —— 不支持 vec![x; n] 语法
macro_rules! my_vec {
// 空向量
() => {
Vec::new()
};
// 单个元素
($elem:expr) => {
{
let mut v = Vec::new();
v.push($elem);
v
}
};
// 多个元素,用逗号分隔
($($x:expr),+ $(,)?) => {
{
let mut v = Vec::new();
$(
v.push($x);
)+
v
}
};
}
fn main() {
// 使用内置 vec! 宏
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);
// 使用自定义 my_vec! 宏
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);
// 验证功能一致
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! ===");
}
输出:
=== 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! 的使用(难度 ⭐)
// ============================================
// 演示内置宏:todo! / unimplemented! / format! / eprintln!
// 场景:一个正在开发中的库存管理系统
// ============================================
// 模拟库存项
#[derive(Debug)]
struct InventoryItem {
id: u32,
name: String,
quantity: u32,
}
// 库存管理器
struct InventoryManager {
items: Vec<InventoryItem>,
}
impl InventoryManager {
fn new() -> InventoryManager {
InventoryManager {
items: Vec::new(),
}
}
// 已实现:添加商品
fn add_item(&mut self, id: u32, name: &str, quantity: u32) {
self.items.push(InventoryItem {
id,
name: name.to_string(),
quantity,
});
// 使用 format! 宏格式化日志消息
let log_msg = format!("[INFO] Added item: {} (id={}, qty={})", name, id, quantity);
println!("{}", log_msg);
}
// 已实现:查找商品
fn find_item(&self, id: u32) -> Option<&InventoryItem> {
self.items.iter().find(|item| item.id == id)
}
// 未实现:更新库存
fn update_quantity(&mut self, _id: u32, _new_qty: u32) {
// TODO: 实现库存更新逻辑
// todo!() 会 panic 并提示"未实现"
todo!("update_quantity: id={} quantity={}", _id, _new_qty);
}
// 未实现:生成库存报告
fn generate_report(&self) -> String {
// unimplemented!() 表示这个功能还没做
unimplemented!("generate_report() is not yet implemented");
}
// 已实现:打印所有商品
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 ===");
// 添加商品
manager.add_item(101, "Laptop", 10);
manager.add_item(102, "Mouse", 50);
manager.add_item(103, "Keyboard", 30);
// 列出商品
println!("\nCurrent inventory:");
manager.list_items();
// 查找商品
if let Some(item) = manager.find_item(102) {
println!("\nFound: {:?}", item);
}
// 以下代码取消注释会 panic(但不会编译错误):
// 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.");
}
输出:
=== 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!()更适合标记接口定义中暂不实现的方法。两者都会在运行时 panic,但不会导致编译错误——让你可以先把代码写完整再逐步实现。
▶ 示例 5:宏的卫生性(Hygiene)——宏内部变量不污染外部(难度 ⭐⭐⭐)
// ============================================
// 演示宏的卫生性(hygiene)
// 宏内部创建的变量不会与外部作用域冲突
// ============================================
// 注意:Rust 声明宏的卫生性是"部分卫生"的
// 宏内部用 $ 捕获的变量名不会与外部冲突
// 但宏内部直接写的标识符在 Rust 2018+ 中有特殊规则
// 宏:创建一个局部变量 tmp 并做交换
// 注意:宏内部直接写 tmp 是卫生的——不会影响外部的 tmp
macro_rules! swap_with_tmp {
($a:expr, $b:expr) => {
{
let tmp = $a;
$a = $b;
$b = tmp;
}
};
}
// 宏:演示卫生性 —— 即使外部也有 tmp 变量
macro_rules! demonstrate_hygiene {
($x:expr) => {
{
// 宏内部定义的 tmp 是卫生的
let tmp = $x * 2;
println!(" Inside macro: tmp = {}", tmp);
tmp
}
};
}
// 非卫生的反例(用另一种方式展示)
// 注意:Rust 声明宏不允许直接创建跨作用域冲突的变量
// 所以这里用 concat 模拟"非卫生"可能导致的问题
fn main() {
println!("=== Macro Hygiene Demonstration ===\n");
// 场景 1:宏内部变量不影响外部
println!("1. Macro internal variable vs external variable:");
let mut x = 10;
let mut y = 20;
println!(" Before swap: x={}, y={}", x, y);
// 宏内部使用了 tmp,但外部的变量名 tmp 不受影响
swap_with_tmp!(x, y);
println!(" After swap: x={}, y={}", x, y);
// 外部的 tmp 变量并不存在 —— 宏内部的 tmp 是卫生的
// 下面这行取消注释会编译错误:
// println!("tmp from macro = {}", tmp); // ❌ 编译错误: tmp not found
// 场景 2:宏内部变量与外部同名变量不冲突
println!("\n2. Hygiene with same name:");
let tmp = 100; // 外部 tmp
println!(" Outside macro: tmp = {}", tmp);
let result = demonstrate_hygiene!(5);
println!(" Return value: {}", result);
println!(" Outside macro again: tmp = {}", tmp); // 仍然是 100,不受宏影响
// 场景 3:为什么卫生性重要
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 ===");
}
输出:
=== 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.
宏的卫生性(hygiene)是 Rust 声明宏的一个重要特性:宏内部创建的变量名不会"泄漏"到调用者的作用域中。在上面的示例中,宏内部有
tmp,外部也有tmp,但它们互不干扰。这避免了 C 语言宏中常见的"名字冲突"问题——在 C 里,如果宏内部用了一个叫tmp的变量,而调用者恰好也有一个tmp,就会产生难以排查的 Bug。
▶ 示例 6:综合示例——用宏构建迷你测试框架(难度 ⭐⭐⭐)
// ============================================
// 综合示例:用宏构建一个迷你单元测试框架
// 结合使用:匹配模式、重复模式、内置宏
// ============================================
// 测试结果统计
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_rules! test_suite {
// 匹配零个或多个测试
($( $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");
// 用宏定义测试套件
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
};
// 打印统计
stats.print_summary();
// 验证所有测试通过
assert_eq!(stats.total, 8);
assert_eq!(stats.passed, 8);
assert_eq!(stats.failed, 0);
println!("\n=== Demo completed ===");
}
输出:
=== 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 行。更重要的是——如果需要增加"测试超时"或"测试分组"功能,只需要改宏模板一处,所有测试自动升级。
❓ 常见问题
macro_rules! 中的 $()* 和 $()+ 有什么区别?$()* 匹配零次或多次重复(允许空),$()+ 匹配一次或多次重复(至少一次)。todo!() 和 unimplemented!() 有什么区别?📖 小节
macro_rules!是 Rust 的声明宏系统,通过模式匹配将输入的 token 流替换为代码模板,在编译期展开- 重复模式
$()*(零次或多次)和$()+(一次或多次)是宏实现可变参数的核心机制 - 常见内置宏
vec!、println!、format!、todo!、unimplemented!本质都是声明宏,vec!的展开就是Vec::new()+ 重复push - 函数 vs 宏:函数操作运行时值,宏操作编译期代码;函数参数固定,宏参数可变;函数有类型检查,宏展开后才有类型检查
- 宏的卫生性(hygiene)保证宏内部变量不会泄漏到外部作用域,避免 C 语言宏的名字冲突问题
- 过程宏是更高级的宏系统,包含派生宏、属性宏、函数宏三种,需要在独立的
proc-macrocrate 中定义
📝 作业
- 难度 ⭐:写一个
make_pair!宏,接收两个表达式,返回一个元组(expr1, expr2)。例如make_pair!(42, "hello")展开为(42, "hello")。在main中调用并打印结果。 - 难度 ⭐⭐:写一个
assert_equal!宏,接收两个表达式,如果它们相等就打印✅ PASS,否则打印❌ FAIL: left != right。使用stringify!宏来打印表达式原文。创建至少 3 个测试用例(包含相等和不相等的情况)并运行。 - 难度 ⭐⭐⭐:写一个
create_enum_with_display!宏,接收枚举名称和一组变体名称,自动生成枚举定义 + 实现Displaytrait(每个变体显示为对应的字符串)。例如create_enum_with_display!(Color, Red, Green, Blue)展开为一个Color枚举,其中Red显示为"Red"。提示:使用$()*重复模式和stringify!。