声明宏 macro_rules!
你会学到什么
macro_rules!通过“模式 => 展开”的规则生成代码。- 元变量如
$x:expr、$t:ty捕获语法片段。 $(...),*表示重复,可处理任意数量的参数。
最小示例
macro_rules! my_max {
($a:expr, $b:expr) => {
if $a >= $b { $a } else { $b }
};
}
运行代码
cd examples
cargo run -p rt_39_macros
cargo test -p rt_39_macros
代码讲解
宏在编译期展开,所以 my_max!(3, 7) 直接变成一个 if 表达式,没有函数调用开销,也不限制参数类型。
重复用 $(...)* 表达,下面的宏接受任意多个 key => value:
macro_rules! hashmap {
($($key:expr => $value:expr),* $(,)?) => {{
let mut map = std::collections::HashMap::new();
$( map.insert($key, $value); )*
map
}};
}
$(,)? 允许结尾多写一个逗号。常见元变量:expr(表达式)、ident(标识符)、ty(类型)、tt(token 树)。
常见错误
宏卫生(hygiene)问题:在宏里引入的变量名可能和调用处冲突。把内部变量包在 {{ ... }} 块里、用不易撞名的名字,可减少意外。
练习
- 写一个
vec_of_strings!["a", "b"]宏,生成Vec<String>。 - 给
my_max!扩展成支持 3 个及以上参数(提示:递归调用宏)。
小结
macro_rules! 用模式匹配生成代码,擅长消除重复和构造字面量,是声明宏的主力。
下一步
需要绕过借用检查器做底层操作时,要用 unsafe。下一章学习 unsafe 基础。
完整示例代码
下面是 examples/39_macros/ 的完整源码。无需 clone 仓库,直接在页面上阅读、复制、对照运行。
examples/39_macros/src/main.rs
//! 声明宏 macro_rules!:在编译期生成代码。
/// 取两个值中较大者(演示最简单的宏)。
macro_rules! my_max {
($a:expr, $b:expr) => {
if $a >= $b { $a } else { $b }
};
}
/// 用 `key => value` 字面量构造 HashMap,支持任意个数和结尾逗号。
macro_rules! hashmap {
($($key:expr => $value:expr),* $(,)?) => {{
#[allow(unused_mut)]
let mut map = std::collections::HashMap::new();
$(
map.insert($key, $value);
)*
map
}};
}
fn main() {
println!("my_max = {}", my_max!(3, 7));
let scores = hashmap! {
"alice" => 90,
"bob" => 85,
};
println!("alice = {:?}", scores.get("alice"));
}
#[cfg(test)]
mod tests {
#[test]
fn max_macro() {
assert_eq!(my_max!(3, 7), 7);
assert_eq!(my_max!(9, 2), 9);
}
#[test]
fn hashmap_macro() {
let map = hashmap! {
"a" => 1,
"b" => 2,
};
assert_eq!(map.get("a"), Some(&1));
assert_eq!(map.len(), 2);
}
#[test]
fn hashmap_macro_empty() {
let map: std::collections::HashMap<&str, i32> = hashmap! {};
assert!(map.is_empty());
}
} examples/39_macros/Cargo.toml
[package]
name = "rt_39_macros"
version.workspace = true
edition.workspace = true
publish.workspace = true