
代码生成代码:当程序员开始"偷懒",元编程就诞生了
你有没有写过这样的代码:
fn print_i32(x: i32) {
println!("i32: {}", x);
}
fn print_i64(x: i64) {
println!("i64: {}", x);
}
fn print_u32(x: u32) {
println!("u32: {}", x);
}
fn print_u64(x: u64) {
println!("u64: {}", x);
}
// ... 继续写到第 20 个类型
写到手酸,然后心想:"这些函数除了类型不一样,其他都一模一样啊!能不能让编译器帮我生成?"
恭喜你,你已经有了元编程的思维!
在 Rust 中,这个超能力叫做 宏(Macro)。宏可以帮你:
但宏也是 Rust 中最难掌握的特性之一。有人说:"宏是 Rust 的黑魔法。"
今天,咱们就来揭开这层黑魔法的神秘面纱。
先搞清楚一个核心区别:
函数:在运行时被调用,操作的是值 宏:在编译时被展开,操作的是代码本身
想象一下:
Rust 有两种宏:
macro_rules! 定义!?这是 Rust 的约定:宏的名字后面加 !,提醒你:
"嘿,这不是普通函数,这是宏!展开时机不一样哦!"
常见的宏:
println! - 格式化输出vec! - 创建 Vecformat! - 格式化字符串panic! - 触发 panictodo! - 标记待实现让我们从最简单的开始:
// 定义一个宏
macro_rules! say_hello {
() => {
println!("Hello, Macro!");
};
}
fn main() {
say_hello!(); // 输出:Hello, Macro!
}
宏展开后相当于:
fn main() {
println!("Hello, Macro!");
}
宏的强大之处在于可以接受参数:
macro_rules! print_value {
($value:expr) => {
println!("Value: {}", $value);
};
}
fn main() {
print_value!(); // Value: 42
print_value!("Hello"); // Value: Hello
print_value!(vec![, , ]); // Value: [1, 2, 3]
}
语法解释:
:expr - 匹配器类型(expression)=> - 宏展开的规则宏可以有多个规则,类似 match:
macro_rules! print_type {
// 匹配 i32 类型
($value:expr, i32) => {
println!("i32: {}", $value);
};
// 匹配 String 类型
($value:expr, String) => {
println!("String: {}", $value);
};
// 默认规则
($value:expr, $type:ty) => {
println!("{}: {}", stringify!($type), $value);
};
}
fn main() {
print_type!(, i32); // i32: 42
print_type!("Hi", String); // String: Hi
print_type!(3.14, f64); // f64: 3.14
}
常用匹配器类型:
匹配器 | 匹配内容 | 示例 |
|---|---|---|
:expr | 表达式 | 1 + 2, func() |
:ident | 标识符 | x, my_var |
:literal | 字面量 | 42, "hello" |
:ty | 类型 | i32, Vec<String> |
:pat | 模式 | Some(x), _ |
:stmt | 语句 | let x = 1; |
:tt | Token Tree | 任意 token |
宏可以匹配重复的模式,这是函数做不到的:
macro_rules! vector {
// 匹配零个或多个元素
($($elem:expr),*) => {
{
let mut vec = Vec::new();
$(
vec.push($elem);
)*
vec
}
};
}
fn main() {
let v1 = vector![]; // 空 Vec
let v2 = vector![, , ]; // Vec![1, 2, 3]
let v3 = vector!["a", "b", "c"]; // Vec!["a", "b", "c"]
println!("{:?}", v2); // [1, 2, 3]
}
语法解释:
$(...)* - 重复展开* - 零次或多次(还有 + 一次或多次,? 零次或一次)macro_rules! my_vec {
// 空的情况
() => {
Vec::new()
};
// 有元素的情况
($($elem:expr),+) => {
{
let mut temp_vec = Vec::new();
$(
temp_vec.push($elem);
)+
temp_vec
}
};
}
fn main() {
let v1: Vec<i32> = my_vec![];
let v2 = my_vec![, , , , ];
let v3 = my_vec!["hello", "world"];
println!("v2: {:?}", v2); // [1, 2, 3, 4, 5]
}
宏可以用来创建领域特定语言(DSL):
macro_rules! query {
// SELECT * FROM table
(SELECT * FROM $table:ident) => {
format!("SELECT * FROM {}", stringify!($table))
};
// SELECT columns FROM table WHERE condition
(SELECT $($cols:ident),+ FROM $table:ident WHERE $where:ident = $value:expr) => {
format!(
"SELECT {} FROM {} WHERE {} = {:?}",
stringify!($($cols),+),
stringify!($table),
stringify!($where),
$value
)
};
}
fn main() {
let q1 = query!(SELECT * FROM users);
println!("{}", q1);
// SELECT * FROM users
let q2 = query!(SELECT id, name FROM users WHERE age = );
println!("{}", q2);
// SELECT id, name FROM users WHERE age = 18
}
macro_rules! impl_debug {
($($struct_name:ident { $($field:ident),* }),*) => {
$(
impl std::fmt::Debug for $struct_name {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"{} {{ {} }}",
stringify!($struct_name),
$(
format!("{}: {:?}", stringify!($field), self.$field)
).join(", ")
)
}
}
)*
};
}
// 定义结构体
struct Point {
x: i32,
y: i32,
}
struct Person {
name: String,
age: u32,
}
// 自动生成 Debug 实现
impl_debug!(
Point { x, y },
Person { name, age }
);
fn main() {
let p = Point { x: , y: };
println!("{:?}", p); // Point { x: 1, y: 2 }
let person = Person {
name: "Alice".to_string(),
age: ,
};
println!("{:?}", person); // Person { name: "Alice", age: 30 }
}
mod my_module {
macro_rules! hello {
() => { println!("Hello!"); };
}
}
fn main() {
hello!(); // ❌ 错误:宏不在作用域内
}
// ✅ 正确:导出宏
#[macro_export]
macro_rules! hello {
() => { println!("Hello!"); };
}
macro_rules! add {
($a:expr, $b:expr) => {
$a + $b
};
}
fn main() {
let x = ;
// 宏在编译时展开,不是运行时
let result = add!(x, ); // 展开为:x + 10
println!("{}", result); // 15
}
macro_rules! bad_macro {
($x:expr) => {
{
let x = $x; // 可能覆盖外部变量
x +
}
};
}
fn main() {
let x = ;
let result = bad_macro!(); // x 被覆盖了!
// println!("{}", x); // 编译错误
}
// ✅ 正确:使用唯一变量名
macro_rules! good_macro {
($x:expr) => {
{
let __macro_x = $x;
__macro_x +
}
};
}
macro_rules! complex_macro {
($($tt:tt)*) => {
// 一堆复杂的逻辑
// 出错了都不知道哪有问题
};
}
解决: 使用 cargo expand 查看宏展开后的代码:
cargo install cargo-expand
cargo expand > expanded.rs
让我们实现一个实用的日志宏:
macro_rules! log {
// 基础版本:只有消息
($msg:expr) => {
{
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap();
println!("[{}] {}", now.as_secs(), $msg);
}
};
// 带级别
($level:expr, $msg:expr) => {
{
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap();
println!("[{}][{}] {}", now.as_secs(), $level, $msg);
}
};
// 带格式化参数
($level:expr, $msg:expr, $($args:expr),+) => {
{
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap();
println!("[{}][{}] {}", now.as_secs(), $level, format!($msg, $($args),+));
}
};
}
fn main() {
log!("Application started");
log!("INFO", "Processing request");
log!("ERROR", "Failed to connect: {}", "timeout");
// 输出:
// [1709856000] Application started
// [1709856000][INFO] Processing request
// [1709856000][ERROR] Failed to connect: timeout
}

:expr(表达式)、:ident(标识符)、:ty(类型)、:tt(token tree)cargo expand,查看展开后的代码#[macro_export] 导出到 crate 根金句时间:
宏就像代码打印机——你设计好模板,它帮你批量生产。
学完 macro_rules!,你可能觉得:"这已经够强大了吧?"
且慢!还有更厉害的 过程宏(Procedural Macros) 等着你:
#[derive(MyTrait)])#[my_attribute])my_macro!() 的升级版)敬请期待 第 38 篇:高级宏!