
从 macro_rules! 到过程宏:Rust 元编程的完全体
上篇我们学了 macro_rules!,你已经能用它写一些很酷的宏了。但你可能发现了一些限制:
这时候,你需要 过程宏(Procedural Macros)。
如果说 macro_rules! 是"模板引擎",那过程宏就是"完整的编程语言"。它允许你在编译时运行真正的 Rust 代码来生成代码。
听起来很绕?想象一下:
macro_rules! 就像一个表单模板,你填好空,它生成结果 过程宏 就像一个程序员,你告诉它需求,它现场写代码
今天,咱们就来深入 Rust 元编程的深水区。系好安全带,准备起飞!
Rust 有三种过程宏:
Function-like Macros(函数式宏)
#[proc_macro]
pub fn my_macro(input: TokenStream) -> TokenStream {
// 处理 input,生成输出
}
// 使用
my_macro!(some input);
Derive Macros(派生宏)
#[proc_macro_derive(MyTrait)]
pub fn derive_my_trait(input: TokenStream) -> TokenStream {
// 为结构体/枚举生成 trait 实现
}
// 使用
#[derive(MyTrait)]
struct MyStruct { ... }
Attribute Macros(属性宏)
#[proc_macro_attribute]
pub fn my_attr(args: TokenStream, item: TokenStream) -> TokenStream {
// 修改被标记的项
}
// 使用
#[my_attr]
fn my_function() { ... }
过程宏的核心是 TokenStream——它代表 Rust 代码的词法流。
源代码: fn add(a: i32, b: i32) -> i32 { a + b }
↓ 词法分析
TokenStream: [fn] [add] [(] [a] [:] [i32] [,] [b] [:] [i32] [)] [->] [i32] [{] [a] [+] [b] [}]
过程宏的工作就是:
宏卫生 是指宏中定义的变量不会与外部代码冲突。
// macro_rules! 有卫生保证
macro_rules! make_struct {
($name:ident) => {
struct $name {
value: i32, // 这个 value 不会与外部冲突
}
};
}
// 过程宏没有自动卫生
// 需要手动使用 Span 来保证
为什么重要? 想象你写了一个宏,里面用了变量 x。如果用户代码里也有 x,就会冲突。宏卫生确保这种情况不会发生。
过程宏必须在独立的 crate 中定义:
# 创建过程宏 crate
cargo new --lib my_macros
cd my_macros
Cargo.toml:
[package]
name = "my_macros"
version = "0.1.0"
edition = "2021"
[lib]
proc-macro = true # 关键配置!
[dependencies]
quote = "1.0" # 构建 TokenStream
syn = { version = "2.0", features = ["full"] } # 解析 Rust 代码
proc-macro2 = "1.0" # TokenStream 的替代品
让我们实现一个自定义的 #[derive(Hello)],为结构体自动生成 hello() 方法:
// src/lib.rs
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};
#[proc_macro_derive(Hello)]
pub fn derive_hello(input: TokenStream) -> TokenStream {
// 解析输入
let input = parse_macro_input!(input as DeriveInput);
// 获取结构体名称
let name = &input.ident;
// 生成代码
let expanded = quote! {
impl #name {
pub fn hello(&self) {
println!("Hello from {}!", stringify!(#name));
}
}
};
// 转换为 TokenStream
TokenStream::from(expanded)
}
使用这个宏:
// 主 crate
use my_macros::Hello;
#[derive(Hello)]
struct Person {
name: String,
age: u32,
}
fn main() {
let person = Person {
name: "Alice".to_string(),
age: ,
};
person.hello(); // Hello from Person!
}
// src/lib.rs
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput, Data, Fields};
#[proc_macro_derive(Builder)]
pub fn derive_builder(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = &input.ident;
// 获取字段
let fields = match &input.data {
Data::Struct(data) => match &data.fields {
Fields::Named(fields) => &fields.named,
_ => panic!("Only named fields supported"),
},
_ => panic!("Only structs supported"),
};
// 生成 builder 结构体的字段
let builder_fields = fields.iter().map(|f| {
let field_name = &f.ident;
let field_type = &f.ty;
quote! {
#field_name: Option<#field_type>
}
});
// 生成 build 方法的字段赋值
let build_assignments = fields.iter().map(|f| {
let field_name = &f.ident;
quote! {
#field_name: self.#field_name.ok_or("Field not set")?
}
});
// 生成 builder 方法
let builder_methods = fields.iter().map(|f| {
let field_name = &f.ident;
let field_type = &f.ty;
quote! {
pub fn #field_name(mut self, value: #field_type) -> Self {
self.#field_name = Some(value);
self
}
}
});
let builder_name = syn::Ident::new(
&format!("{}Builder", name),
name.span()
);
let expanded = quote! {
struct #builder_name {
#(#builder_fields,)*
}
impl #name {
pub fn builder() -> #builder_name {
#builder_name {
#(#fields.ident: None,)*
}
}
}
impl #builder_name {
#(#builder_methods)*
pub fn build(self) -> Result<#name, &'static str> {
Ok(#name {
#(#build_assignments,)*
})
}
}
};
TokenStream::from(expanded)
}
使用:
use my_macros::Builder;
#[derive(Builder)]
struct User {
name: String,
email: String,
age: u32,
}
fn main() {
let user = User::builder()
.name("Alice".to_string())
.email("alice@example.com".to_string())
.age()
.build()
.unwrap();
println!("User: {:?}", user);
}
// src/lib.rs
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, ItemFn};
#[proc_macro_attribute]
pub fn log(_args: TokenStream, item: TokenStream) -> TokenStream {
// 解析函数
let input = parse_macro_input!(item as ItemFn);
let fn_name = &input.sig.ident;
let inputs = &input.sig.inputs;
let output = &input.sig.output;
let where_clause = &input.sig.generics.where_clause;
let generics = &input.sig.generics;
let visibility = &input.vis;
let asyncness = &input.sig.asyncness;
let body = &input.block;
// 生成带日志的函数
let expanded = quote! {
#visibility #asyncness fn #fn_name #generics(#inputs) #output #where_clause {
println!("Entering {}", stringify!(#fn_name));
let result = #body;
println!("Exiting {}", stringify!(#fn_name));
result
}
};
TokenStream::from(expanded)
}
使用:
use my_macros::log;
#[log]
fn add(a: i32, b: i32) -> i32 {
a + b
}
#[log]
async fn fetch_data() -> String {
"data".to_string()
}
fn main() {
let result = add(, );
println!("Result: {}", result);
// 输出:
// Entering add
// Exiting add
// Result: 8
}
// src/lib.rs
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, Expr};
#[proc_macro]
pub fn router(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as Expr);
let expanded = quote! {
{
use std::collections::HashMap;
let mut routes: HashMap<&str, Box<dyn Fn()>> = HashMap::new();
// 这里可以解析 input 并生成路由
// 简化示例
routes.insert("/", Box::new(|| println!("Home")));
routes.insert("/about", Box::new(|| println!("About")));
routes
}
};
TokenStream::from(expanded)
}
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};
#[proc_macro_derive(NoConflict)]
pub fn derive_no_conflict(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = &input.ident;
// 使用原始名称的 Span 保证卫生
let internal_var = syn::Ident::new("__internal_var", name.span());
let expanded = quote! {
impl #name {
pub fn process(&self) {
let #internal_var = self;
// 使用 __internal_var 不会与用户代码冲突
println!("Processing...");
}
}
};
TokenStream::from(expanded)
}
# ❌ 错误:没有这个配置
[lib]
name = "my_macros"
# ✅ 正确
[lib]
proc-macro = true
后果: 编译错误,无法使用 #[proc_macro_derive] 等属性。
// ❌ 错误:过程宏 crate 不能直接用 std
use std::println;
// ✅ 正确:使用 proc_macro 提供的 API
use proc_macro::TokenStream;
// ❌ 错误:直接 panic
fn parse_input(input: TokenStream) -> DeriveInput {
syn::parse(input).unwrap() // panic!
}
// ✅ 正确:返回编译错误
fn parse_input(input: TokenStream) -> Result<DeriveInput, syn::Error> {
syn::parse(input)
}
// ❌ 错误:错误没有位置信息
panic!("Only structs supported");
// ✅ 正确:指向具体代码位置
return syn::Error::new(
input.ident.span(),
"Only structs supported"
)
.to_compile_error()
.into();
让我们实现一个简化版的 serde derive:
// src/lib.rs
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput, Data, Fields};
#[proc_macro_derive(Serialize)]
pub fn derive_serialize(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = &input.ident;
// 生成字段序列化代码
let field_serialization = match &input.data {
Data::Struct(data) => match &data.fields {
Fields::Named(fields) => {
let fields = fields.named.iter().map(|f| {
let field_name = &f.ident;
let field_name_str = field_name.as_ref().unwrap().to_string();
quote! {
map.insert(#field_name_str, serde_json::to_value(&self.#field_name).unwrap());
}
});
quote! { #(#fields)* }
}
_ => panic!("Only named fields supported"),
},
_ => panic!("Only structs supported"),
};
let expanded = quote! {
impl #name {
pub fn serialize(&self) -> serde_json::Value {
use std::collections::HashMap;
let mut map = HashMap::new();
#field_serialization
serde_json::Value::Object(
map.into_iter()
.map(|(k, v)| (k.to_string(), v))
.collect()
)
}
}
};
TokenStream::from(expanded)
}
使用:
use my_macros::Serialize;
#[derive(Serialize)]
struct User {
name: String,
age: u32,
email: String,
}
fn main() {
let user = User {
name: "Alice".to_string(),
age: ,
email: "alice@example.com".to_string(),
};
let json = user.serialize();
println!("{}", json);
// {"name": String("Alice"), "age": Number(30), "email": String("alice@example.com")}
}

proc-macro = trueproc-macro(标准库)、quote(生成)、syn(解析)金句时间:
macro_rules! 是模板,过程宏是程序员——一个填空,一个写代码。
恭喜你完成了 Rust 高级主题四部曲:
这些是 Rust 中最强大也最复杂的特性。掌握它们,你就真正进入了 Rust 高手的行列!
高级主题学完了,接下来咱们进入 实战项目篇!
从第 39 篇开始,咱们会用真实的项目来巩固前面学的知识:
敬请期待 第 39 篇:CLI 工具开发!