宏系统与元编程
本章目标: 掌握声明宏和过程宏,理解 Rust 元编程的边界
学习时间: 5-7 天
前置知识: 完成
07-智能指针与内存管理.md
宏的种类
Rust 宏体系:
声明宏 过程宏
(Declarative) (Procedural)
---------- ----------
macro_rules! #[derive(...)] 派生宏
#[attr] 属性宏
func!(...) 函数式宏
1. 声明宏 (macro_rules!)
基础语法
// 最简单的宏
macro_rules! say_hello {
() => {
println!("Hello!");
};
}
// 带参数的宏
macro_rules! print_expr {
($e:expr) => {
println!("{} = {:?}", stringify!($e), $e);
};
}
fn main() {
say_hello!();
let x = 5 + 3;
print_expr!(x); // x = 8
print_expr!(5 + 3); // 5 + 3 = 8
}捕获类型 (Fragment Specifiers)
macro_rules! demo_fragments {
// expr: 表达式
(expr $e:expr) => { println!("expr: {:?}", $e) };
// stmt: 语句
(stmt $s:stmt) => { $s };
// ty: 类型
(ty $t:ty) => { println!("type") };
// ident: 标识符 (变量名、函数名等)
(ident $i:ident) => { println!("ident: {}", stringify!($i)) };
// pat: 模式
(pat $p:pat) => { println!("pattern") };
// path: 路径 (如 std::vec::Vec)
(path $p:path) => { println!("path") };
// tt: 单个 token 或括号内的 token 树 (最灵活)
(tt $t:tt) => { println!("token tree") };
// literal: 字面量
(lit $l:literal) => { println!("literal: {}", $l) };
// block: 代码块 {}
(block $b:block) => { $b };
// item: 条目 (fn/struct/impl等)
(item $i:item) => { $i };
}重复模式
// $( ... )? - 零或一次
// $( ... )* - 零或多次
// $( ... )+ - 一或多次
// 实现 vec! 宏
macro_rules! my_vec {
// 无元素
() => {
Vec::new()
};
// 一或多个元素
($($elem:expr),+ $(,)?) => {
{
let mut v = Vec::new();
$( v.push($elem); )+
v
}
};
}
// 实现 hashmap! 宏
macro_rules! hashmap {
() => { std::collections::HashMap::new() };
($($key:expr => $val:expr),+ $(,)?) => {
{
let mut map = std::collections::HashMap::new();
$( map.insert($key, $val); )+
map
}
};
}
fn main() {
let v = my_vec![1, 2, 3, 4, 5];
println!("{:?}", v);
let m = hashmap! {
"name" => "Alice",
"city" => "Beijing",
};
println!("{:?}", m);
}递归宏
// 使用递归实现更复杂的模式
// 实现 println! 的简化版
macro_rules! my_print {
($fmt:literal) => {
print!($fmt);
};
($fmt:literal, $($args:expr),*) => {
print!($fmt, $($args),*);
};
}
// 实现 count! - 计算参数个数
macro_rules! count {
() => { 0usize };
($head:tt $($tail:tt)*) => { 1 + count!($($tail)*) };
}
fn main() {
println!("count: {}", count!(a b c d e)); // 5
}实用宏示例
// 1. 调试宏
macro_rules! dbg_val {
($val:expr) => {
{
let v = $val;
eprintln!("[{}:{}] {} = {:?}", file!(), line!(), stringify!($val), v);
v
}
};
}
// 2. 断言宏 (带自定义消息)
macro_rules! ensure {
($cond:expr, $($msg:tt)*) => {
if !($cond) {
return Err(format!($($msg)*));
}
};
}
fn validate_age(age: i32) -> Result<(), String> {
ensure!(age >= 0, "Age cannot be negative, got {}", age);
ensure!(age <= 150, "Age {} is unreasonably large", age);
Ok(())
}
// 3. 枚举字符串转换宏
macro_rules! str_enum {
($name:ident { $($variant:ident),+ }) => {
#[derive(Debug, Clone, PartialEq)]
pub enum $name {
$($variant),+
}
impl std::fmt::Display for $name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
$( $name::$variant => write!(f, stringify!($variant)), )+
}
}
}
impl std::str::FromStr for $name {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
$( stringify!($variant) => Ok($name::$variant), )+
_ => Err(format!("Unknown variant: {}", s)),
}
}
}
};
}
str_enum!(Color { Red, Green, Blue });
fn main() {
let c: Color = "Red".parse().unwrap();
println!("{}", c); // Red
println!("{:?}", c); // Red
}2. 过程宏基础
过程宏是编译时运行的 Rust 函数,接受 TokenStream 并返回 TokenStream。
项目结构
my_macros/ # 必须是单独的 crate!
├── Cargo.toml
└── src/
└── lib.rs
my_app/
├── Cargo.toml # 依赖 my_macros
└── src/
└── main.rs
# my_macros/Cargo.toml
[lib]
proc-macro = true
[dependencies]
proc-macro2 = "1.0"
quote = "1.0"
syn = { version = "2.0", features = ["full"] }核心工具库
proc-macro2: TokenStream 类型 (可组合)
syn: 解析 Rust 语法树
quote: 从代码模板生成 TokenStream
3. 派生宏 (#[derive(…)])
// my_macros/src/lib.rs
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput, Data, Fields};
// 实现 #[derive(Hello)]
#[proc_macro_derive(Hello)]
pub fn hello_derive(input: TokenStream) -> TokenStream {
// 解析输入为语法树
let input = parse_macro_input!(input as DeriveInput);
let name = &input.ident;
// 生成实现代码
let expanded = quote! {
impl Hello for #name {
fn hello(&self) -> String {
format!("Hello, I am {}!", stringify!(#name))
}
}
};
TokenStream::from(expanded)
}// 使用:
use my_macros::Hello;
pub trait Hello {
fn hello(&self) -> String;
}
#[derive(Hello)]
struct MyStruct {
name: String,
}
fn main() {
let s = MyStruct { name: "Alice".into() };
println!("{}", s.hello()); // Hello, I am MyStruct!
}带字段的派生宏
// 实现 #[derive(Builder)] - 自动生成 Builder 模式
#[proc_macro_derive(Builder)]
pub fn builder_derive(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = &input.ident;
let builder_name = syn::Ident::new(
&format!("{}Builder", name),
name.span()
);
// 提取字段
let fields = match &input.data {
Data::Struct(s) => match &s.fields {
Fields::Named(f) => &f.named,
_ => panic!("Only named fields supported"),
},
_ => panic!("Only structs supported"),
};
// 生成 Builder 字段 (用 Option 包裹)
let builder_fields = fields.iter().map(|f| {
let fname = &f.ident;
let ftype = &f.ty;
quote! { #fname: Option<#ftype> }
});
// 生成 setter 方法
let setters = fields.iter().map(|f| {
let fname = &f.ident;
let ftype = &f.ty;
quote! {
pub fn #fname(mut self, value: #ftype) -> Self {
self.#fname = Some(value);
self
}
}
});
// 生成 build 方法
let build_fields = fields.iter().map(|f| {
let fname = &f.ident;
quote! {
#fname: self.#fname.ok_or(
concat!("Field '", stringify!(#fname), "' not set")
)?
}
});
let expanded = quote! {
// Builder 结构体
pub struct #builder_name {
#(#builder_fields,)*
}
impl #builder_name {
#(#setters)*
pub fn build(self) -> Result<#name, &'static str> {
Ok(#name {
#(#build_fields,)*
})
}
}
impl #name {
pub fn builder() -> #builder_name {
#builder_name {
#(#(#fields.ident)?: None,)*
}
}
}
};
// 注意: 上面的展开有语法问题, 实际项目中需要更仔细处理字段初始化
TokenStream::from(expanded)
}4. 属性宏
属性宏可以修改被标注的条目:
// 实现 #[logged] 属性宏 - 自动记录函数调用
#[proc_macro_attribute]
pub fn logged(args: TokenStream, input: TokenStream) -> TokenStream {
let input_fn = parse_macro_input!(input as syn::ItemFn);
let fn_name = &input_fn.sig.ident;
let fn_block = &input_fn.block;
let fn_sig = &input_fn.sig;
let fn_vis = &input_fn.vis;
let expanded = quote! {
#fn_vis #fn_sig {
println!("[LOG] Entering {}", stringify!(#fn_name));
let result = (|| #fn_block)();
println!("[LOG] Exiting {}", stringify!(#fn_name));
result
}
};
TokenStream::from(expanded)
}// 使用:
use my_macros::logged;
#[logged]
fn process_data(x: i32) -> i32 {
x * 2
}
fn main() {
let result = process_data(21);
println!("Result: {}", result);
}
// 输出:
// [LOG] Entering process_data
// [LOG] Exiting process_data
// Result: 425. 函数式过程宏
// 实现 sql! 宏 - 编译时验证 SQL 语法
#[proc_macro]
pub fn sql(input: TokenStream) -> TokenStream {
let input_str = input.to_string();
// 简单验证 (实际应该用 SQL 解析器)
if !input_str.trim_matches('"').to_uppercase().starts_with("SELECT")
&& !input_str.trim_matches('"').to_uppercase().starts_with("INSERT")
&& !input_str.trim_matches('"').to_uppercase().starts_with("UPDATE")
&& !input_str.trim_matches('"').to_uppercase().starts_with("DELETE")
{
// 编译时错误
return quote! {
compile_error!("Only SELECT/INSERT/UPDATE/DELETE are supported")
}.into();
}
// 返回字符串
quote! { #input_str }.into()
}6. 调试技巧
cargo-expand
# 安装
cargo install cargo-expand
# 展开宏
cargo expand # 所有宏
cargo expand src/main.rs # 特定文件
cargo expand --test # 测试文件中的宏eprintln! 调试过程宏
#[proc_macro_derive(Debug2)]
pub fn debug2_derive(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
// 打印 AST (调试用)
eprintln!("Input AST: {:#?}", input);
// 生成代码
let name = &input.ident;
let expanded = quote! {
impl std::fmt::Debug for #name {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", stringify!(#name))
}
}
};
// 打印生成的代码 (调试用)
eprintln!("Generated: {}", expanded);
TokenStream::from(expanded)
}常见错误
// 错误1: 卫生性问题 (Hygiene)
macro_rules! bad_swap {
($a:expr, $b:expr) => {
let tmp = $a; // tmp 在宏内部是独立的
$a = $b;
$b = tmp; // 这里 tmp 是宏内的 tmp, 不是外部同名变量
};
}
// 正确: 使用块表达式
macro_rules! good_swap {
($a:ident, $b:ident) => {
{
let tmp = $a;
$a = $b;
$b = tmp;
}
};
}
// 错误2: $crate 的使用
// 在库宏中引用本 crate 的路径要用 $crate::
macro_rules! create_error {
($msg:literal) => {
$crate::Error::new($msg) // 正确! 不要用 crate::Error
};
}练习题
练习 1: 实现 assert_eq_approx! 宏
// 实现浮点数近似相等断言
macro_rules! assert_approx_eq {
($a:expr, $b:expr) => {
assert_approx_eq!($a, $b, 1e-6)
};
($a:expr, $b:expr, $eps:expr) => {
{
let a = $a;
let b = $b;
let eps = $eps;
if (a - b).abs() > eps {
panic!(
"assertion failed: `(left ≈ right)`\n left: `{:?}`,\n right: `{:?}`,\n eps: `{:?}`",
a, b, eps
);
}
}
};
}
fn main() {
assert_approx_eq!(0.1 + 0.2, 0.3); // 通过 (在 1e-6 精度内)
assert_approx_eq!(1.0, 1.0000001, 1e-5); // 通过
// assert_approx_eq!(1.0, 2.0); // panic!
}练习 2: 实现 lazy_static! 宏
// 实现线程安全的懒加载宏 (简化版)
use std::sync::OnceLock;
macro_rules! lazy_static_simple {
(static ref $name:ident: $type:ty = $init:expr;) => {
static $name: OnceLock<$type> = OnceLock::new();
// 注意: OnceLock 已经提供了 get_or_init
// 实际 lazy_static 更复杂, 这里演示原理
};
}
// 更简洁的方式: 使用 std::sync::OnceLock
static GLOBAL_CONFIG: OnceLock<std::collections::HashMap<String, String>> = OnceLock::new();
fn get_config() -> &'static std::collections::HashMap<String, String> {
GLOBAL_CONFIG.get_or_init(|| {
let mut m = std::collections::HashMap::new();
m.insert("host".to_string(), "localhost".to_string());
m.insert("port".to_string(), "8080".to_string());
m
})
}
fn main() {
println!("host = {}", get_config().get("host").unwrap());
}练习 3: 实现状态机宏
macro_rules! state_machine {
(
enum $name:ident {
$($state:ident),+
}
transitions {
$($from:ident -> $to:ident via $event:ident),+
}
) => {
#[derive(Debug, Clone, PartialEq)]
pub enum $name {
$($state),+
}
impl $name {
pub fn transition(&self, event: &str) -> Option<$name> {
match (self, event) {
$( ($name::$from, stringify!($event)) => Some($name::$to), )+
_ => None,
}
}
}
};
}
state_machine! {
enum TrafficLight {
Red,
Yellow,
Green
}
transitions {
Red -> Green via go,
Green -> Yellow via slow,
Yellow -> Red via stop
}
}
fn main() {
let mut state = TrafficLight::Red;
println!("{:?}", state);
state = state.transition("go").unwrap_or(state.clone());
println!("{:?}", state); // Green
state = state.transition("slow").unwrap_or(state.clone());
println!("{:?}", state); // Yellow
}练习 4: 使用 cargo-expand 分析
// 展开 derive(Debug) 后是什么?
#[derive(Debug)]
struct Point {
x: f64,
y: f64,
}
// 运行: cargo expand
// 会看到类似:
// impl std::fmt::Debug for Point {
// fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
// f.debug_struct("Point")
// .field("x", &self.x)
// .field("y", &self.y)
// .finish()
// }
// }练习 5: 自定义 println! 变体
// 带时间戳的日志宏
macro_rules! log {
($level:ident, $($args:tt)*) => {
eprintln!(
"[{level}] {msg}",
level = stringify!($level),
msg = format!($($args)*)
);
};
}
macro_rules! info {
($($args:tt)*) => { log!(INFO, $($args)*) };
}
macro_rules! warn {
($($args:tt)*) => { log!(WARN, $($args)*) };
}
macro_rules! error {
($($args:tt)*) => { log!(ERROR, $($args)*) };
}
fn main() {
info!("Server started on port {}", 8080);
warn!("Memory usage: {}%", 85);
error!("Failed to connect to {}", "database");
}练习 6: 综合 - 实现 serde 风格的序列化宏
// 实现简单的 JSON 序列化 derive 宏 (概念演示)
// 真实项目请使用 serde crate
trait JsonSerialize {
fn to_json(&self) -> String;
}
// 手动实现 (派生宏的效果)
struct User {
name: String,
age: u32,
}
impl JsonSerialize for User {
fn to_json(&self) -> String {
format!(
r#"{{"name": "{}", "age": {}}}"#,
self.name, self.age
)
}
}
// 假如有 #[derive(JsonSerialize)] 宏, 它应该生成上面的代码
// 这就是过程宏的作用: 消除样板代码
fn main() {
let u = User {
name: "Alice".to_string(),
age: 30,
};
println!("{}", u.to_json());
}何时使用宏?
使用宏的好场景:
✅ 消除大量样板代码 (derive)
✅ 创建 DSL (领域特定语言)
✅ 编译时验证 (SQL 语法等)
✅ 实现 variadic 函数 (vec!, println!)
避免使用宏的情况:
❌ 能用函数或泛型实现的 → 用函数/泛型
❌ 逻辑复杂但 DSL 价值不高 → 直接写代码
❌ 调试困难的复杂宏 → 考虑替代方案
经验法则:
- 如果宏调用比它实现的功能更难理解 → 不要用宏
- 如果宏减少了超过 3 处重复代码 → 可以考虑
小结
| 宏类型 | 语法 | 适用场景 |
|---|---|---|
macro_rules! | name!(...) | 模式匹配展开, 简单重复代码 |
#[derive(X)] | #[derive(X)] | 自动实现 trait |
属性宏 #[attr] | #[attr] | 转换代码, AOP |
| 函数式过程宏 | name!(...) | 编译时计算, DSL |
下一步
- 完成 6 道练习题
- 运行
cargo expand观察标准宏的展开 - 阅读
09-异步编程与Runtime.md - 尝试给自己的项目写一个简单的派生宏
掌握宏系统, 你就拥有了在 Rust 中创造语言的能力! 🦀