Rust 宏开发完全指南
Rust 的宏系统是其最强大的特性之一,能够在编译时生成代码,大大减少样板代码并提高代码的可维护性。本文将深入介绍 Rust 宏开发的各个方面,包括声明式宏(macro_rules!)和过程宏(Procedural Macros)。
宏概述
什么是宏?
宏是一种在编译时执行代码生成的技术。与函数不同:
- 函数:复用行为
- 宏:复用语法和结构
宏接受 Rust 代码作为输入,返回新的 Rust 代码作为输出。这使得我们可以自动化编写重复性的代码。
Rust 宏的两种类型
- 声明式宏(Declarative Macros):使用
macro_rules!定义,基于模式匹配 - 过程宏(Procedural Macros):像函数一样工作,操作 TokenStream
声明式宏
声明式宏使用 macro_rules! 定义,是 Rust 中最基本的宏形式。
基本语法
macro_rules! my_macro {
() => {
// 空参数匹配
};
($val:expr) => {
// 匹配表达式
};
}第一个声明式宏
macro_rules! say_hello {
() => {
println!("Hello, World!");
};
}
fn main() {
say_hello!();
}模式匹配
macro_rules! print_if_true {
($condition:expr, $message:expr) => {
if $condition {
println!("{}", $message);
}
};
}
fn main() {
print_if_true!(true, "Condition was true!");
print_if_true!(false, "This won't print");
}重复模式
$()* 用于匹配零个或多个,$()+ 用于一个或多个:
macro_rules! vec_of_strings {
($($val:expr),*) => {
{
let mut temp_vec = Vec::new();
$(
temp_vec.push(format!("{:?}", $val));
)*
temp_vec
}
};
}
fn main() {
let v = vec_of_strings!(1, "hello", 3.14, true);
println!("{:?}", v);
}生成多个函数
macro_rules! define_functions {
($($name:ident),*) => {
$(
fn $name() {
println!(stringify!($name));
}
)*
};
}
define_functions!(foo, bar, baz);
fn main() {
foo();
bar();
baz();
}哈希映射宏
use std::collections::HashMap;
macro_rules! hash_map {
($($key:expr => $value:expr),* $(,)?) => {
{
let mut map = HashMap::new();
$(
map.insert($key, $value);
)*
map
}
};
}
fn main() {
let scores = hash_map! {
"Alice" => 100,
"Bob" => 85,
"Charlie" => 92,
};
println!("{:?}", scores);
}过程宏基础
过程宏是 Rust 中更强大的宏形式,它们像函数一样操作代码的 TokenStream。
三种过程宏类型
- derive 宏:
#[derive(...)]- 为结构体和枚举自动实现 trait - 属性宏:
#[attribute]- 修改代码项的行为 - 函数式宏:
macro!()- 像函数调用一样使用
项目设置
过程宏必须定义在独立的 crate 中:
# Cargo.toml for the macro crate
[package]
name = "my_macro_derive"
version = "0.1.0"
edition = "2021"
[lib]
proc-macro = true
[dependencies]
syn = { version = "2.0", features = ["full", "extra-traits"] }
quote = "1.0"
proc-macro2 = "1.0"
darling = "0.20" # 可选:简化属性解析核心依赖说明
- proc_macro2:
proc_macrocrate 的 wrapper,提供兼容的 TokenStream 类型 - syn:解析 Rust 代码为 AST,提供可操作的结构
- quote:使用模板语法生成 Rust 代码
Derive 宏开发
Derive 宏是最常用的过程宏类型,用于自动实现 trait。
第一个 Derive 宏
Step 1: 创建 trait crate
// hello_macro/src/lib.rs
pub trait Hello {
fn hello();
}
Step 2: 创建 derive 宏 crate
// hello_macro_derive/src/lib.rs
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};
#[proc_macro_derive(Hello)]
pub fn hello_derive(input: TokenStream) -> TokenStream {
// 1. 解析输入 TokenStream 为 AST
let ast = parse_macro_input!(input as DeriveInput);
// 2. 获取结构体/枚举名称
let name = &ast.ident;
// 3. 使用 quote! 生成代码
let expanded = quote! {
impl Hello for #name {
fn hello() {
println!("Hello from {}!", stringify!(#name));
}
}
};
// 4. 转换为 TokenStream
expanded.into()
}
Step 3: 使用宏
use hello_macro::Hello;
use hello_macro_derive::Hello;
#[derive(Hello)]
struct Pancakes;
#[derive(Hello)]
enum MyEnum {
VariantA,
VariantB,
}
fn main() {
Pancakes::hello(); // 输出: Hello from Pancakes!
}访问结构体字段
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput, Data, Fields};
#[proc_macro_derive(Describe)]
pub fn describe_derive(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
let name = &ast.ident;
let fields_description = match &ast.data {
Data::Struct(data) => {
match &data.fields {
Fields::Named(fields) => {
let field_names: Vec<_> = fields
.named
.iter()
.map(|f| f.ident.as_ref().unwrap())
.collect();
quote! {
let mut desc = String::new();
$(
desc.push_str(&format!("{}: {:?}\n", stringify!(#field_names), self.#field_names));
)*
desc
}
}
Fields::Unnamed(fields) => {
quote! {
format!("{} with {} fields", stringify!(#name), fields.len())
}
}
Fields::Unit => {
quote! { stringify!(#name).to_string() }
}
}
}
Data::Enum(_) | Data::Union(_) => {
quote! { stringify!(#name).to_string() }
}
};
let expanded = quote! {
impl Describe for #name {
fn describe(&self) -> String {
#fields_description
}
}
};
expanded.into()
}解析 Derive 宏参数
use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::{parse_macro_input, Attribute, Data, DeriveInput, Fields, Ident, Meta};
#[proc_macro_derive(Builder, attributes(builder))]
pub fn derive(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
let name = &ast.ident;
// 解析 builder 属性的参数
let builder_name = if let Some(builder_attr) = ast.attrs.iter().find(|a| a.path().is_ident("builder")) {
let meta = builder_attr.meta();
if let Meta::NameValue(nv) = meta {
if let syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(s), .. }) = &nv.value {
Some(s.value())
} else {
None
}
} else {
None
}
} else {
None
};
let builder_ident = match builder_name {
Some(n) => format_ident!("{}", n),
None => format_ident!("{}Builder", name),
};
let fields = if let Data::Struct(data) = &ast.data {
if let Fields::Named(fields) = &data.fields {
&fields.named
} else {
panic!("Builder only works on structs with named fields");
}
} else {
panic!("Builder only works on structs");
};
let field_names: Vec<_> = fields.iter().map(|f| f.ident.as_ref().unwrap()).collect();
let field_types: Vec<_> = fields.iter().map(|f| &f.ty).collect();
let expanded = quote! {
pub struct #builder_ident {
#(#field_names: Option<#field_types>,)*
}
impl #builder_ident {
pub fn new() -> Self {
Self {
#(#field_names: None,)*
}
}
$(
pub fn #field_names(mut self, val: #field_types) -> Self {
self.#field_names = Some(val);
self
}
)*
pub fn build(&self) -> #name {
#name {
#(#field_names: self.#field_names.clone().unwrap(),)*
}
}
}
impl Default for #builder_ident {
fn default() -> Self {
Self::new()
}
}
};
expanded.into()
}
使用:
use builder::Builder;
#[derive(Builder)]
struct CreateUserRequest {
name: String,
email: String,
age: u32,
}
fn main() {
let request = CreateUserRequest::builder()
.name("Alice".to_string())
.email("alice@example.com".to_string())
.age(30)
.build();
}Derive 宏辅助属性
#[proc_macro_derive(ToQueryParams, attributes(query_param))]
pub fn derive_to_query_params(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
let name = &ast.ident;
let fields = if let Data::Struct(data) = &ast.data {
&data.fields
} else {
panic!("ToQueryParams only works on structs");
};
let field_impls: Vec<_> = fields.iter().map(|f| {
let field_name = f.ident.as_ref().unwrap();
let field_str = field_name.to_string();
// 检查是否有 query_param 属性
let rename = f.attrs.iter()
.find(|a| a.path().is_ident("query_param"))
.and_then(|a| {
let meta = a.meta();
if let Meta::NameValue(nv) = meta {
if let syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(s), .. }) = &nv.value {
return Some(s.value());
}
}
None
});
let param_name = rename.unwrap_or(field_str);
quote! {
if let Some(ref v) = self.#field_name {
params.push((#param_name, v.to_string()));
}
}
}).collect();
let expanded = quote! {
impl #name {
pub fn to_query_params(&self) -> Vec<(&str, String)> {
let mut params = Vec::new();
#(#field_impls)*
params
}
}
};
expanded.into()
}
使用辅助属性:
#[derive(ToQueryParams)]
struct SearchParams {
#[query_param(rename = "q")]
query: Option<String>,
#[query_param(rename = "page_size")]
size: Option<u32>,
}属性宏开发
属性宏可以附加到任何代码项上(函数、结构体、模块等)。
基本属性宏
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, ItemFn};
#[proc_macro_attribute]
pub fn log_execution(attr: TokenStream, item: TokenStream) -> TokenStream {
// attr: 属性括号内的内容
// item: 属性附加到的代码项
let input = parse_macro_input!(item as ItemFn);
let func_name = &input.sig.ident;
let func_body = &input.block;
let expanded = quote! {
fn #func_name() {
println!("Calling {}...", stringify!(#func_name));
let start = std::time::Instant::now();
let result = (|| #func_body)();
println!("{} took {:?}", stringify!(#func_name), start.elapsed());
result
}
};
expanded.into()
}
使用:
#[log_execution]
fn my_function() {
println!("Doing work...");
}
fn main() {
my_function();
}解析属性参数
use proc_macro::{TokenStream, TokenTree, Delimiter};
use quote::quote;
use syn::{parse_macro_input, Attribute, ItemFn, Lit, Meta};
#[proc_macro_attribute]
pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream {
let attr_tokens: Vec<_> = attr.clone().into_iter().collect();
// 解析 "GET", "/path" 格式
let method = &attr_tokens[0];
let path = &attr_tokens[1];
let input = parse_macro_input!(item as ItemFn);
let func_name = &input.sig.ident;
let func_body = &input.block;
let expanded = quote! {
fn #func_name(req: actix_web::HttpRequest) -> impl actix_web::Responder {
println!("Route {} {}", #method, #path);
(|| #func_body)()
}
};
expanded.into()
}
使用:
#[route(GET, "/users")]
fn get_users() -> String {
"List of users".to_string()
}
#[route(POST, "/users")]
fn create_user() -> String {
"User created".to_string()
}验证属性宏
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, Data, DeriveInput, Fields};
#[proc_macro_derive(Validate, attributes(validate))]
pub fn validate_derive(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
let name = &ast.ident;
let validation_code = if let Data::Struct(data) = &ast.data {
if let Fields::Named(fields) = &data.fields {
fields.named.iter().map(|f| {
let field_name = f.ident.as_ref().unwrap();
let mut checks = Vec::new();
for attr in &f.attrs {
if attr.path().is_ident("validate") {
let meta = attr.meta();
if let syn::Meta::List(list) = meta {
for meta in &list.meta {
if let syn::Meta::NameValue(nv) = meta {
if let syn::Expr::Path(p) = &nv.value {
let constraint = p.path.last().unwrap();
match constraint.to_string().as_str() {
"non_empty" => {
checks.push(quote! {
if self.#field_name.is_empty() {
return Err(format!("{} must not be empty", stringify!(#field_name)));
}
});
}
"positive" => {
checks.push(quote! {
if self.#field_name <= 0 {
return Err(format!("{} must be positive", stringify!(#field_name)));
}
});
}
_ => {}
}
}
}
}
}
}
}
checks
}).flatten().collect::<Vec<_>>()
} else {
Vec::new()
}
} else {
Vec::new()
};
let expanded = quote! {
impl Validate for #name {
fn validate(&self) -> Result<(), String> {
#(#validation_code)*
Ok(())
}
}
};
expanded.into()
}
使用:
use validate::Validate;
#[derive(Validate)]
struct CreateUserRequest {
#[validate(non_empty)]
name: String,
#[validate(positive)]
age: u32,
}函数式宏开发
函数式宏像函数调用一样使用,但操作 TokenStream。
基本函数式宏
use proc_macro::TokenStream;
use quote::quote;
#[proc_macro]
pub fn make_answer(_item: TokenStream) -> TokenStream {
let expanded = quote! {
fn answer() -> u32 {
42
}
};
expanded.into()
}
使用:
use make_answer::make_answer;
make_answer!();
fn main() {
println!("{}", answer()); // 输出: 42
}SQL 查询宏
use proc_macro::TokenStream;
use quote::quote;
#[proc_macro]
pub fn sql(input: TokenStream) -> TokenStream {
let sql_string = input.to_string();
let expanded = quote! {
{
static STATEMENT: once_cell::sync::Lazy<tokio_postgres::Statement> =
once_cell::sync::Lazy::new(|| {
let client = DATABASE.get().unwrap();
futures::executor::block_on(client.prepare(#sql_string))
.expect("Failed to prepare statement")
});
STATEMENT
}
};
expanded.into()
}
使用:
let users = sql!(
SELECT id, name, email
FROM users
WHERE active = true
).fetch_all(&*client).await?;哈希宏
use proc_macro::TokenStream;
use quote::quote;
use syn::parse_str;
#[proc_macro]
pub fn hashmap(input: TokenStream) -> TokenStream {
let input_str = input.to_string();
let expanded = quote! {
{
let mut map = std::collections::HashMap::new();
// 解析并填充 map
let items: Vec<&str> = #input_str.split(',').collect();
for item in items {
let parts: Vec<&str> = item.split("=>").collect();
if parts.len() == 2 {
map.insert(parts[0].trim(), parts[1].trim());
}
}
map
}
};
expanded.into()
}测试宏
使用 trybuild 测试
[dev-dependencies]
trybuild = "1.0"// tests/ui/simple.rs
use my_macro::MyTrait;
#[derive(MyTrait)]
struct TestStruct {
field_a: i32,
field_b: String,
}
fn main() {
let instance = TestStruct {
field_a: 42,
field_b: "test".to_string(),
};
instance.my_trait_method();
}// tests/my_macro_tests.rs
#[test]
fn test_derive() {
let t = trybuild::TestCases::new();
t.pass("tests/ui/simple.rs");
t.compile_fail("tests/ui/error*.rs");
}单元测试
#[cfg(test)]
mod tests {
use super::*;
use quote::quote;
#[test]
fn test_derive_simple_struct() {
let input = quote! {
struct Point {
x: f64,
y: f64,
}
};
let output = derive(input);
assert!(output.to_string().contains("impl Describe for Point"));
}
#[test]
fn test_derive_with_generics() {
let input = quote! {
struct Wrapper<T> {
value: T,
}
};
let output = derive(input);
assert!(output.to_string().contains("impl<T> Describe for Wrapper<T>"));
}
}调试技巧
使用 cargo-expand
cargo install cargo-expand
cargo expand --lib
这会打印出宏展开后的代码:
$ cargo expand
#![feature(prelude_import)]
#[prelude_import]
pub use ::std::prelude::rust_2021::*;
...打印调试信息
#[proc_macro_derive(Hello)]
pub fn hello_derive(input: TokenStream) -> TokenStream {
eprintln!("Input: {}", input); // 打印到 stderr
let ast = syn::parse(input).unwrap();
eprintln!("Parsed: {:?}", ast);
// ... 宏逻辑
}使用 proc-macro2 进行测试
#[cfg(test)]
mod tests {
use proc_macro2::TokenStream;
use quote::quote;
use syn::parse_quote;
#[test]
fn test_macro() {
let input: TokenStream = quote! {
struct Test {
field: i32,
}
};
let ast: syn::DeriveInput = syn::parse2(input).unwrap();
assert_eq!(ast.ident, "Test");
}
}最佳实践
1. 分离关注点
// 分离解析和代码生成
#[proc_macro_derive(MyTrait, attributes(my_attr))]
pub fn my_trait_derive(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
impl_my_trait(&ast).into()
}
fn impl_my_trait(ast: &DeriveInput) -> proc_macro2::TokenStream {
// 代码生成逻辑
let name = &ast.ident;
// ...
}2. 提供清晰的错误消息
#[proc_macro_derive(MyTrait)]
pub fn my_trait_derive(input: TokenStream) -> TokenStream {
let ast = match syn::parse(input) {
Ok(ast) => ast,
Err(e) => return e.to_compile_error().into(),
};
if let syn::Data::Struct(_) = &ast.data {
// OK
} else {
return syn::Error::new_spanned(
&ast,
"MyTrait only supports structs",
).to_compile_error().into();
}
// ...
}3. 保持宏专注
// 好:单一职责
#[proc_macro_derive(Debug))]
#[proc_macro_derive(Clone))]
#[proc_macro_derive(Serialize)]
// 不好:试图做太多
#[proc_macro_derive(AllTheThings)]4. 处理卫生性
// quote! 自动处理标识符卫生性
let var = quote!(value); // 生成的标识符是卫生的
// 如果需要非卫生的标识符
let var = syn::Ident::new("value", proc_macro2::Span::混在一起());5. 支持泛型和生命周期
#[proc_macro_derive(Describe)]
pub fn describe_derive(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
let name = &ast.ident;
let (impl_generics, ty_generics, where_clause) = &ast.generics.split_for_impl();
let expanded = quote! {
impl #impl_generics Describe for #name #ty_generics #where_clause {
// ...
}
};
expanded.into()
}常见宏库示例
实现 Default for Builder
#[proc_macro_derive(Builder, attributes(builder))]
pub fn builder_derive(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
let name = &ast.ident;
let builder_name = format_ident!("{}Builder", name);
let fields = if let Data::Struct(data) = &ast.data {
if let Fields::Named(fields) = &data.fields {
&fields.named
} else {
return quote! {
compile_error!("Builder requires named fields");
}.into();
}
} else {
return quote! {
compile_error!("Builder only works on structs");
}.into();
};
let field_names: Vec<_> = fields.iter().map(|f| &f.ident).collect();
let field_types: Vec<_> = fields.iter().map(|f| &f.ty).collect();
let expanded = quote! {
pub struct #builder_name {
#(#field_names: core::option::Option<#field_types>,)*
}
impl #builder_name {
pub fn new() -> Self {
Self {
#(#field_names: core::option::Option::None,)*
}
}
#(
pub fn #field_names(mut self, val: #field_types) -> Self {
self.#field_names = core::option::Option::Some(val);
self
}
)*
pub fn build(self) -> #name {
#name {
#(#field_names: self.#field_names.unwrap(),)*
}
}
}
impl Default for #builder_name {
fn default() -> Self {
Self::new()
}
}
};
expanded.into()
}总结
Rust 的宏系统提供了强大的编译时代码生成能力:
- 声明式宏:
macro_rules!- 适合简单的模式匹配和代码生成 - 过程宏:
- Derive 宏 - 自动实现 trait(如
#[derive(Debug)]) - 属性宏 - 修改或增强代码项
- 函数式宏 - 像函数一样使用的 DSL
- Derive 宏 - 自动实现 trait(如
关键工具:
- syn:解析 Rust 代码为 AST
- quote:使用模板生成 Rust 代码
- proc-macro2:TokenStream 的跨平台兼容层
最佳实践:
- 保持宏专注和单一职责
- 提供清晰的错误消息
- 使用 cargo-expand 调试
- 处理泛型和生命周期
- 编写测试确保正确性
掌握宏开发,你将能够构建像 serde、diesel、rocket 这样强大的 Rust 库!
快乐编程,大家来 Rust! 🦀