Rust 的宏(macro) 是一种在编译期进行代码生成或转换的机制,它允许你编写“生成代码的代码”,从而减少重复、提升表达力,并实现一些普通函数无法做到的功能(比如定义 DSL、处理可变参数等)。
与函数不同,宏在编译阶段展开,不产生运行时开销(零成本抽象)。
一、宏的类型
Rust 主要有两类宏:
| 类型 | 说明 | 语法 |
|---|---|---|
| 声明宏(Declarative Macros) | 类似“模板匹配”,通过模式匹配生成代码 | macro_rules! |
| 过程宏(Procedural Macros) | 用 Rust 代码操作 AST,功能更强(如 #[derive(...)]) |
#[proc_macro], #[proc_macro_attribute], #[proc_macro_derive] |
本回答重点介绍声明宏和标准库中的预定义宏(因过程宏较复杂,常用于高级场景)。
二、什么是 macro_rules! 宏?(声明宏)
基本语法
macro_rules! 宏名 {(模式) => { 生成的代码 };(另一个模式) => { 另一段代码 };
}
示例:自定义宏
macro_rules! say_hello {() => {println!("Hello!");};($name:expr) => {println!("Hello, {}!", $name);};
}fn main() {say_hello!(); // → Hello!say_hello!("Alice"); // → Hello, Alice!
}
✅ 宏调用以
!结尾(如println!),这是 Rust 识别宏的方式。
三、标准库中的常用预定义宏(无需导入,自动可用)
以下是 Rust 标准库(std) 提供的、最常用且预定义的宏(部分需 use,但多数在 prelude 中):
1. println! / print!
作用:格式化输出到 stdout。
println!("Hello, {}!", "world");
println!("{name} is {age} years old", name = "Alice", age = 30);
2. format!
作用:格式化生成 String(不打印)。
let s = format!("Hello, {}!", "Rust");
// s: String = "Hello, Rust!"
3. vec!
作用:创建 Vec<T>。
let v1 = vec![1, 2, 3];
let v2 = vec![0; 5]; // [0, 0, 0, 0, 0]
4. assert! / assert_eq! / assert_ne!
作用:调试断言,失败时 panic。
assert!(x > 0);
assert_eq!(a, b); // 如果 a != b,panic 并显示差异
assert_ne!(a, b); // 如果 a == b,panic
5. panic!
作用:主动触发 panic。
panic!("Something went wrong!");
6. dbg!
作用:调试打印表达式的值和位置(返回原值)。
let x = dbg!(2 + 3); // 输出:[src/main.rs:2] 2 + 3 = 5
7. todo! / unimplemented!
作用:占位符,表示“尚未实现”,调用时 panic。
fn new_feature() -> String {todo!("Implement this later")
}
8. include_str! / include_bytes!
作用:在编译期将文件内容嵌入代码。
let txt = include_str!("config.txt"); // &str
let bin = include_bytes!("data.bin"); // &[u8]
✅ 文件路径相对于源文件所在目录。
9. concat! / env! / file! / line!
作用:编译期常量操作。
let msg = concat!("Hello", " ", "World"); // "Hello World"
let rustc = env!("RUSTC"); // 编译器路径
println!("File: {}, Line: {}", file!(), line!());
10. write! / writeln!
作用:向实现了 std::fmt::Write 的对象写入格式化内容(如 String、文件)。
use std::fmt::Write;
let mut output = String::new();
writeln!(&mut output, "Result: {}", 42).unwrap();
四、宏 vs 函数 的关键区别
| 特性 | 宏 | 函数 |
|---|---|---|
| 调用语法 | name!(...) |
name(...) |
| 参数数量 | 可变(如 println!("", a, b, c)) |
固定 |
| 类型检查时机 | 展开后才类型检查 | 调用时立即检查 |
| 能力 | 可生成任意代码(如定义新变量、结构体) | 只能执行逻辑 |
| 性能 | 零成本(编译期展开) | 有函数调用开销(可能被内联) |
五、总结
- 宏是 Rust 的“编译期代码生成器”,强大且零成本。
- 标准库提供了大量实用宏,覆盖 I/O、断言、调试、初始化等场景。
- 调用宏必须加
!,这是识别宏的关键。 - 优先使用标准宏(如
vec!,format!),它们安全、高效、惯用。 - 自定义宏适用于消除重复代码或构建 DSL,但应谨慎使用(可读性可能降低)。
💡 记住:
你每天写的println!,vec!,assert_eq!都是宏——它们让 Rust 既安全又富有表现力。