🦀 Rust 宏与元编程

宏是 Rust 元编程的核心武器。从 vec! 的简洁语法到过程宏的代码生成,宏让你在编译期操作代码本身。但能力越大,陷阱越深——从基础到实战,掌握宏的正确打开方式。

macro_rules! 过程宏 Derive Attribute宏 TokenStream 代码生成

📑 目录

  1. 宏 vs 函数
  2. macro_rules! 声明宏
  3. 常用宏解析
  4. 过程宏入门
  5. Derive 宏实战
  6. Attribute 宏
  7. 宏的常见模式
  8. 宏的陷阱
  9. 声明宏 vs 过程宏选择
  10. 实战:用过程宏实现 ORM 字段映射

1. 宏 vs 函数

宏和函数看起来都能"复用代码",但它们运作在不同的维度上。函数操作值,宏操作代码。理解这个本质区别,是正确使用宏的第一步。

1.1 编译期展开 vs 运行期调用

┌─── 函数 ───────────────────────────────────────────────────────┐ │ 源码 → 编译 → call f(x,y) → 运行时跳转执行 → 返回 │ │ ↑ ↑ │ │ 类型检查在此完成 参数运行时求值 │ └──────────────────────────────────────────────────────────────────┘ ┌─── 宏 ─────────────────────────────────────────────────────────┐ │ 源码 → 宏展开 → 展开后的代码 → 编译 → 正常执行 │ │ ↑ ↑ │ │ 模式匹配替换 展开结果再接受类型检查 │ └──────────────────────────────────────────────────────────────────┘

关键区别:宏在编译期就被展开成 Rust 代码,编译器看到的是展开后的结果,而不是宏调用本身。

1.2 宏能做到而函数做不到的事

🔄 可变参数

Rust 函数不支持可变参数。println!vec! 能接受任意数量参数,因为宏可以根据模式生成不同的代码。

🏗️ 代码生成

宏可以生成结构体、impl 块、trait 实现等声明性代码。函数只能生成值和执行逻辑,不能凭空"长出"新的类型定义。

🎯 编译期计算

concat!env!include_str! 在编译期就完成计算,结果直接嵌入二进制。函数只能运行时计算。

📝 DSL 构建

宏可以定义自己的语法规则,构建领域特定语言。如 sqlx::query! 编译期验证 SQL 语法。

1.3 何时用宏、何时不用

✅ 适合用宏

  • 需要可变参数
  • 需要生成类型/trait/impl
  • 需要编译期计算或验证
  • 构建 DSL(SQL、HTML 模板等)
  • 消除大量重复的样板代码
  • 需要捕获调用位置的文件名/行号

❌ 不该用宏

  • 普通逻辑用函数就够了
  • 只是"少写几个字"
  • 宏展开后代码很难调试
  • 团队中有人不熟悉宏
  • 运行时行为依赖隐藏逻辑
  • 可以用泛型/trait 解决的问题

⚠️ 黄金法则:如果你能用函数+泛型+trait 解决,就不要用宏。宏是最后的抽象手段,不是第一选择。

2. macro_rules! 声明宏

声明宏是 Rust 最常见的宏形式。原理类似模式匹配:你定义一系列规则,编译器按照规则把输入 token 替换成输出 token。

2.1 基本语法

macro_rules!
macro_rules! say_hello {
    () => {
        println!("Hello, world!");
    };
}

macro_rules! create_string {
    ($s:expr) => {
        String::from($s)
    };
}

macro_rules! return_number {
    (one)   => { 1 };
    (two)   => { 2 };
    (three) => { 3 };
}

say_hello!();                     // → println!("Hello, world!");
let s = create_string!("hello");  // → String::from("hello")
let n = return_number!(two);      // → 2

2.2 片段分类器

分类器匹配内容示例
expr表达式1 + 2f()true
ident标识符xMyStruct
ty类型i32Vec<String>
path路径std::collections::HashMap
pat模式Some(x)_
literal字面量42"hello"
tttoken tree任何单个 token 或括号组
itemfn foo() {}struct Bar;
block代码块{ let x = 1; x }
meta元属性derive(Debug)
stmt语句let x = 1;

🔑 tt 是万能钥匙tt 匹配任何单个 token tree(标识符、标点、字面量,或一对括号及其全部内容)。最灵活但也最不安全——编译器不会帮你做语法检查。

2.3 重复模式

语法:$($var:frag) sep rep,其中 sep 是分隔符(可选),rep 是 * / + / ?

重复模式
macro_rules! hashmap {
    ({ $($key:expr => $val:expr),* $(,)? }) => {{
        let mut m = ::std::collections::HashMap::new();
        $( m.insert($key, $val); )*
        m
    }};
}

let scores = hashmap!({
    "Alice" => 95,
    "Bob" => 87,
    "Carol" => 92,
});

💡 尾部逗号技巧$(,)? 允许最后一个元素后面有可选逗号。Rust 宏惯例,方便增删元素时不需要调整逗号。

2.4 递归宏

递归宏:计数元素
macro_rules! count {
    () => (0);
    ($first:tt $($rest:tt)*) => (1 + count!($($rest)*));
}

// count!(a, b, c) → 1 + count!(b, c) → 1 + 1 + count!(c) → 1 + 1 + 1 + count!() → 3

macro_rules! impl_from_variants {
    // 基础:处理最后一个
    ($enum:ident, $var:ident($type:ty)) => {
        impl From<$type> for $enum {
            fn from(v: $type) -> Self { $enum::$var(v) }
        }
    };
    // 递归:处理一个,继续下一个
    ($enum:ident, $var:ident($type:ty), $( $rv:ident($rt:ty) ),+) => {
        impl From<$type> for $enum {
            fn from(v: $type) -> Self { $enum::$var(v) }
        }
        impl_from_variants!($enum, $( $rv($rt) ),+);
    };
}

2.5 宏的卫生性

卫生性
fn main() {
    let x = 10;
    macro_rules! using_var {
        () => { println!("x = {}", x) };
    }
    using_var!();  // ✅ 编译通过,宏展开后能访问外部 x

    macro_rules! safe_macro {
        () => { let __internal = 999; };
    }
    let __internal = 1;
    safe_macro!();
    assert_eq!(__internal, 1);  // ✅ 宏内部变量不会污染外部
}

2.6 宏调试技巧

调试工具
# 1. cargo expand — 查看宏展开后的代码(最常用!)
cargo install cargo-expand
cargo expand

# 2. rustc -Z unpretty=expanded
rustc -Z unpretty=expanded src/main.rs

# 3. compile_error! 打印调试信息
macro_rules! debug_macro {
    ($($tokens:tt)*) => {
        compile_error!(stringify!($($tokens)*));
    };
}

# 4. 递归深度上限(默认 128)
#![recursion_limit = "256"]

3. 常用宏解析

标准库中有大量精心设计的宏,拆解它们是学习宏设计的最佳途径。

3.1 vec!

vec! 源码简化版
macro_rules! vec {
    () => (::std::vec::Vec::new());
    ($elem:expr; $n:expr) => (::std::vec::from_elem($elem, $n));
    ($($x:expr),+ $(,)?) => (
        <[_]>::into_vec(Box::new([$($x),+]))
    );
}

let v1 = vec![];           // Vec::new()
let v2 = vec![0; 100];    // from_elem(0, 100)
let v3 = vec![1, 2, 3];   // into_vec(Box::new([1,2,3]))

🔑 性能细节vec![1, 2, 3] 先在栈上创建数组 [1, 2, 3],再 Box::new 移到堆上,转为 Vec。大小编译期已知,无需动态扩容。

3.2 println!

println!
macro_rules! println {
    () => ($crate::print!("\n"));
    ($($arg:tt)*) => {{
        $crate::io::_print($crate::format_args!($($arg)*));
    }};
}

// format_args! 是编译器内置宏,编译期验证:
//   - {} 数量与参数数量匹配
//   - 格式说明符语法正确(:?, :x, :08 等)
//   - 参数类型与格式说明符兼容

println!("Name: {}, Age: {}", name, age);  // 位置参数
println!("{0} {1} {0}", a, b);           // 复用参数
println!("{name} is {age}", name="A", age=30); // 命名参数

3.3 assert! / assert_eq!

assert 系列
macro_rules! assert_eq {
    ($left:expr, $right:expr $(,)?) => {{
        match (&$left, &$right) {
            (l, r) => {
                if !(*l == *r) {
                    panic!("assertion failed\n  left: {:?}\n right: {:?}", l, r);
                }
            }
        }
    }};
}

// 关键:先用 & 借用再比较——避免消耗值
let a = String::from("hello");
assert_eq!(a, "hello"); // ✅ a 只是借用,之后还能用

3.4 matches!

matches!
macro_rules! matches {
    ($expression:expr, $( $pattern:pat )|+ $( if $guard: expr )? $(,)?) => {
        match $expression {
            $( $pattern )|+ $( if $guard )? => true,
            _ => false
        }
    };
}

enum Value { Int(i32), Str(String), Bool(bool) }
let v = Value::Int(42);

assert!(matches!(v, Value::Int(_)));
assert!(matches!(v, Value::Int(1..=100)));  // 范围模式
assert!(!matches!(v, Value::Str(_)));

// 多模式
let x = Some(5);
assert!(matches!(x, Some(1) | Some(5) | Some(10)));

3.5 todo! / compile_error!

占位与编译错误
// todo! — 标记待实现,运行到此 panic
fn process(data: &[u8]) -> Result<()> {
    todo!("handle binary data");
}

// compile_error! — 编译期报错,用于宏条件分支
macro_rules! require_nightly {
    () => {
        #[cfg(not(feature = "nightly"))]
        compile_error!("This feature requires nightly Rust");
    };
}

3.6 concat! / stringify! / file! / line!

编译期信息宏
// concat! — 编译期字符串拼接
const DB_PATH: &str = concat!("/var/data/", env!("USER"), "/db");

// stringify! — token 转字符串
macro_rules! show_expr {
    ($e:expr) => { println!("{} = {:?}", stringify!($e), $e) };
}
show_expr!(1 + 2); // 输出: 1 + 2 = 3

// file! / line! — 源码位置
fn log(msg: &str) {
    println!("[{}:{}] {}", file!(), line!(), msg);
}

// module_path! — 当前模块路径
mod server {
    pub fn identify() {
        println!("I am {}", module_path!()); // "crate::server"
    }
}

4. 过程宏入门

过程宏是 Rust 元编程的高端武器。不同于 macro_rules! 的模式匹配替换,过程宏直接操作 token 流——你自己写 Rust 代码来生成 Rust 代码。

4.1 三种过程宏

┌─── 过程宏分类 ──────────────────────────────────────────────────┐ │ │ │ 1. Derive 宏 #[derive(Debug, Clone, MyTrait)] │ │ → 为 struct/enum 自动生成 trait 实现 │ │ │ │ 2. Attribute 宏 #[my_attr(arg1, arg2)] │ │ → 修饰任意 item,可以修改/替换/包装代码 │ │ │ │ 3. Function-like 宏 my_macro!(input) │ │ → 外观像 macro_rules! 宏,内部用过程宏实现 │ │ │ └───────────────────────────────────────────────────────────────────┘ 共同点: 输入 TokenStream → 你的代码 → 输出 TokenStream

4.2 项目结构

项目结构
# 过程宏必须在单独的 crate 中
my_macro_lib/
├── Cargo.toml
│   [lib]
│   proc-macro = true
│
│   [dependencies]
│   syn = "2"
│   quote = "1"
│   proc-macro2 = "1"
│
└── src/
    └── lib.rs

4.3 核心三件套

作用比喻
synTokenStream → Rust AST阅读理解:文本变结构
quote代码模板 → TokenStream写作:结构变代码
proc-macro2可测试版本草稿纸:单元测试

4.4 最小 Derive 宏

lib.rs
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};

#[proc_macro_derive(HelloMacro)]
pub fn hello_macro_derive(input: TokenStream) -> TokenStream {
    let ast = parse_macro_input!(input as DeriveInput);
    let name = &ast.ident;

    let gen = quote! {
        impl HelloMacro for #name {
            fn hello_macro() {
                println!("Hello, Macro! My name is {}!", stringify!(#name));
            }
        }
    };

    gen.into()
}

// 使用方
use my_macro_lib::HelloMacro;

#[derive(HelloMacro)]
struct Pancakes;

Pancakes::hello_macro(); // Hello, Macro! My name is Pancakes!

4.5 处理属性和泛型

带辅助属性的 Derive
#[proc_macro_derive(Builder, attributes(builder))]
pub fn builder_derive(input: TokenStream) -> TokenStream {
    let ast = parse_macro_input!(input as DeriveInput);
    // attributes(builder) 允许宏识别 #[builder(...)] 属性
    
    impl_builder(&ast)
        .unwrap_or_else(|e| e.to_compile_error().into())
}

fn impl_builder(ast: &DeriveInput) -> syn::Result<TokenStream> {
    let name = &ast.ident;
    let (impl_generics, ty_generics, where_clause) = 
        ast.generics.split_for_impl();

    let fields = match &ast.data {
        syn::Data::Struct(syn::DataStruct {
            fields: syn::Fields::Named(ref fields),
            ..
        }) => &fields.named,
        _ => return Err(syn::Error::new_spanned(
            ast, "Builder only supports structs with named fields"
        )),
    };

    // 生成 Builder 代码...
    let builder_name = format_ident!("{}Builder", name);
    
    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 #impl_generics #where_clause {
            #(
                #field_names: Option<#field_types>,
            )*
        }

        impl #impl_generics #name #ty_generics #where_clause {
            pub fn builder() -> #builder_name #ty_generics {
                #builder_name {
                    #( #field_names: None, )*
                }
            }
        }
    };

    Ok(expanded.into())
}

4.6 Attribute 宏入门

Attribute 宏
#[proc_macro_attribute]
pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream {
    // attr: 属性参数   → "/api/users"
    // item: 修饰的项   → fn get_users() { ... }
    let path = parse_macro_input!(attr as LitStr);
    let func = parse_macro_input!(item as ItemFn);
    
    let func_name = &func.sig.ident;
    
    let expanded = quote! {
        #func
        
        ::my_framework::register_route(#path, #func_name);
    };
    
    expanded.into()
}

// 使用
#[route("/api/users")]
fn get_users() -> Json<Vec<User>> { ... }

4.7 Function-like 过程宏

Function-like 过程宏
#[proc_macro]
pub fn sql(input: TokenStream) -> TokenStream {
    let query = parse_macro_input!(input as LitStr);
    // 编译期验证 SQL 语法、检查表名和列名
    validate_sql(&query.value())
        .map_err(|e| syn::Error::new_spanned(&query, e))
        .unwrap_or_else(|e| e.to_compile_error().into());
    
    quote! {
        #query
    }.into()
}

// 使用
let query = sql!("SELECT * FROM users WHERE id = $1");

5. Derive 宏实战

5.1 自动实现 Builder 模式

Builder 模式在 Rust 中极其常见,但手写非常繁琐。Derive 宏可以自动生成全部样板代码。

Builder Derive 宏完整实现
use proc_macro::TokenStream;
use quote::{quote, format_ident};
use syn::{parse_macro_input, DeriveInput, Data, Fields};

#[proc_macro_derive(Builder)]
pub fn derive_builder(input: TokenStream) -> TokenStream {
    let ast = parse_macro_input!(input as DeriveInput);
    let name = &ast.ident;
    let builder_name = format_ident!("{}Builder", name);

    let fields = match &ast.data {
        Data::Struct(data) => match &data.fields {
            Fields::Named(ref f) => &f.named,
            _ => panic!("Only named fields supported"),
        },
        _ => panic!("Only structs supported"),
    };

    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: Option<#field_types>, )*
        }

        impl #name {
            pub fn builder() -> #builder_name {
                #builder_name {
                    #( #field_names: None, )*
                }
            }
        }

        impl #builder_name {
            #(
                pub fn #field_names(mut self, val: #field_types) -> Self {
                    self.#field_names = Some(val);
                    self
                }
            )*

            pub fn build(self) -> Result<#name, String> {
                Ok(#name {
                    #( #field_names: self.#field_names
                        .ok_or(format!("field {} is required", stringify!(#field_names)))?, )*
                })
            }
        }
    };

    expanded.into()
}

// 使用方
#[derive(Builder)]
struct User {
    name: String,
    age: u32,
    email: String,
}

let user = User::builder()
    .name("Alice".into())
    .age(30)
    .email("alice@example.com".into())
    .build()?;

5.2 自动生成验证代码

Validate Derive
#[proc_macro_derive(Validate, attributes(validate))]
pub fn derive_validate(input: TokenStream) -> TokenStream {
    let ast = parse_macro_input!(input as DeriveInput);
    let name = &ast.ident;

    let fields = get_named_fields(&ast);
    
    let validations = fields.iter().map(|f| {
        let fname = &f.ident;
        let mut checks = Vec::new();
        
        for attr in f.attrs.iter().filter(|a| a.path().is_ident("validate")) {
            // 解析 #[validate(length(min = 1, max = 100))]
            // 解析 #[validate(range(min = 0, max = 150))]
            // 解析 #[validate(email))]
            // 解析 #[validate(custom = "validate_username"))]
        }
        
        quote! {
            #( #checks )*
        }
    });

    let expanded = quote! {
        impl Validate for #name {
            fn validate(&self) -> Result<(), Vec<String>> {
                let mut errors = Vec::new();
                #( #validations )*
                if errors.is_empty() { Ok(()) } else { Err(errors) }
            }
        }
    };

    expanded.into()
}

// 使用
#[derive(Validate)]
struct SignupForm {
    #[validate(length(min = 1, max = 50))]
    username: String,
    #[validate(email)]
    email: String,
    #[validate(range(min = 0, max = 150))]
    age: u32,
}

form.validate()?;

💡 设计原则:Derive 宏生成的代码应该是"如果手写会怎么写"的模板化版本。先手写一次完整的实现,再抽象成宏,比直接写宏容易得多。

6. Attribute 宏

Attribute 宏是最灵活的过程宏类型。它可以修饰任意 item(函数、结构体、模块等),完全控制生成什么代码。

6.1 工作原理

Attribute 宏输入 #[my_attr(arg1, arg2)] ← attr: TokenStream (属性参数) fn my_func() { ... } ← item: TokenStream (被修饰的项) ↓ 处理 ↓ 输出: TokenStream (可以是修改后的 item + 额外代码)

6.2 实战:异步函数包装器

类似 tokio::main
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, ItemFn, ReturnType};

#[proc_macro_attribute]
pub fn async_main(_attr: TokenStream, item: TokenStream) -> TokenStream {
    let func = parse_macro_input!(item as ItemFn);
    let func_name = &func.sig.ident;
    let func_body = &func.block;
    let func_inputs = &func.sig.inputs;
    
    let expanded = quote! {
        fn #func_name() {
            async fn inner(#func_inputs) #func_body
            tokio::runtime::Runtime::new()
                .unwrap()
                .block_on(inner())
        }
    };
    
    expanded.into()
}

// 使用:把 async fn 变成同步入口
#[async_main]
async fn main() {
    let resp = reqwest::get("https://example.com").await?;
    println!("{}", resp.text().await?);
}

6.3 常见库的 Attribute 宏

作用
tokio#[tokio::main]自动包装 async fn 为 runtime 入口
serde#[serde(rename_all = "camelCase")]控制序列化字段名格式
derive_more#[derive(Display)]自动生成 Display 等常用 trait
tracing#[tracing::instrument]自动为函数添加 tracing span
pyo3#[pyfunction]将 Rust 函数导出为 Python 函数
rocket#[get("/path")]HTTP 路由注册
sqlx#[sqlx::test]自动设置测试数据库

6.4 属性解析

解析属性参数
use syn::parse::{Parse, ParseStream};
use syn::{Ident, Token, LitStr};

struct RouteArgs {
    method: Ident,
    path: LitStr,
}

impl Parse for RouteArgs {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let method: Ident = input.parse()?;
        input.parse::<Token![,]>()?;
        let path: LitStr = input.parse()?;
        Ok(RouteArgs { method, path })
    }
}

#[proc_macro_attribute]
pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream {
    let args = parse_macro_input!(attr as RouteArgs);
    let func = parse_macro_input!(item as ItemFn);
    
    let method = &args.method;
    let path = &args.path;
    let func_name = &func.sig.ident;
    
    let expanded = quote! {
        #func
        
        ::my_framework::routes::register(
            ::my_framework::Method::#method,
            #path,
            #func_name,
        );
    };
    
    expanded.into()
}

7. 宏的常见模式

7.1 DSL 构建

宏最常见的"杀手级"应用是构建 DSL(领域特定语言),让代码表达更贴近问题域。

测试 DSL
macro_rules! describe {
    ($name:expr, { $($body:tt)* }) => {
        mod test_module {
            use super::*;
            $($body)*
        }
    };
}

macro_rules! it {
    ($desc:expr, $body:expr) => {
        #[test]
        fn it() { $body }
    };
}

describe!("Vec operations", {
    it!("pushes and pops", {
        let mut v = vec![1, 2];
        v.push(3);
        assert_eq!(v.pop(), Some(3));
    });
    
    it!("has correct length", {
        let v = vec![1, 2, 3];
        assert_eq!(v.len(), 3);
    });
});

7.2 代码生成

批量生成 trait 实现
macro_rules! impl_display_for_enum {
    ($enum:ident { $($variant:ident),+ $(,)? }) => {
        impl std::fmt::Display for $enum {
            fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
                match self {
                    $( $enum::$variant => write!(f, stringify!($variant)), )+
                }
            }
        }
    };
}

enum Color { Red, Green, Blue }
impl_display_for_enum!(Color { Red, Green, Blue });

assert_eq!(Color::Red.to_string(), "Red");

7.3 配置驱动生成

从配置生成代码
// 定义路由表
macro_rules! define_routes {
    ($($method:ident $path:literal => $handler:ident),+ $(,)?) => {
        pub fn setup_routes(app: &mut App) {
            $(
                app.route($path, RouteMethod::$method, $handler);
            )+
        }
        
        pub fn route_count() -> usize {
            [+ $( 1 )* ] .len()  // 编译期计数
        }
    };
}

define_routes! {
    GET  "/"              => index,
    GET  "/users"         => list_users,
    POST "/users"         => create_user,
    GET  "/users/:id"     => get_user,
    PUT  "/users/:id"     => update_user,
    DELETE "/users/:id"   => delete_user,
}

7.4 减少样板代码

减少样板
// 为多个类型实现相同逻辑
macro_rules! impl_numeric_ops {
    ($($type:ty),*) => {
        $(
            impl MyOps for $type {
                fn double(&self) -> Self { *self * 2 }
                fn squared(&self) -> Self { *self * *self }
                fn is_positive(&self) -> bool { *self > 0 }
            }
        )*
    };
}

impl_numeric_ops!(i8, i16, i32, i64, i128, u8, u16, u32, u64, u128, f32, f64);

// 新类型模式 + Delegation
macro_rules! delegate {
    ($type:ident, $inner:ty, $trait:ident $(, $method:ident)*) => {
        impl $trait for $type {
            $( fn $method(&self) -> <$inner as $trait>::Output {
                self.0.$method()
            } )*
        }
    };
}

8. 宏的陷阱

宏是双刃剑。了解常见陷阱,才能避开它们。

8.1 编译错误信息不友好

错误信息对比
// 宏展开前的代码
let v = hashmap!({
    "name" => 42,     // ← 类型错误:期望 String,得到 i32
});

// 编译器报错可能指向宏定义内部,而不是你的调用处
// error[E0277]: the trait bound `i32: Into` is not satisfied
//   --> src/macros.rs:5:12      ← 指向宏定义,而非调用处!

// ✅ 改进:在宏中加入更好的错误提示
macro_rules! hashmap {
    ({ $($key:expr => $val:expr),* $(,)? }) => {{
        let mut m = ::std::collections::HashMap::new();
        $(
            m.insert(
                $key,
                // 显式标注类型,让错误更清晰
                { let v: _ = $val; v }
            );
        )*
        m
    }};
}

8.2 调试困难

🚨 最大痛点:宏展开后的代码看不到,出了问题只能靠猜。必须使用 cargo expand 查看实际生成的代码。过程宏中用 syn::Error::new_spanned 提供精确的错误位置。

8.3 代码膨胀

代码膨胀
// ❌ 每次调用都生成完整代码
macro_rules! big_impl {
    ($name:ident) => {
        impl $name {
            fn method1(&self) { /* 50 行 */ }
            fn method2(&self) { /* 50 行 */ }
            fn method3(&self) { /* 50 行 */ }
            // ... 更多方法
        }
    };
}

big_impl!(TypeA);
big_impl!(TypeB);
big_impl!(TypeC);  // 生成 3 × N 行代码!

// ✅ 改进:用泛型 + trait 减少膨胀
trait BigOps {
    fn method1(&self);
    fn method2(&self);
    fn method3(&self);
}

impl<T: Common> BigOps for T {
    fn method1(&self) { /* 只有一份实现 */ }
    // ...
}

8.4 IDE 支持差

8.5 过度使用

✅ 合理使用

  • 项目中有 10+ 个结构体需要相同逻辑
  • 编译期验证能避免运行时错误
  • DSL 让代码更易读
  • 标准库和生态中常见的模式

❌ 过度使用

  • 只用一次的宏(直接写吧)
  • 为了"优雅"而隐藏关键逻辑
  • 嵌套宏超过 2 层
  • 宏生成宏(debug 地狱)

9. 声明宏 vs 过程宏选择

9.1 决策树

Q1: 需要生成类型/impl/新代码吗?
否 → 用函数或泛型
是 → 继续
Q2: 需要解析属性或复杂语法吗?
是 → 过程宏
否 → 继续
Q3: 需要可变参数或简单模式匹配?
是 → 声明宏
否 → 继续
Q4: 需要修改/包装已有代码?
是 → Attribute 过程宏
否 → 声明宏(优先简单方案)

9.2 对比表

维度声明宏 (macro_rules!)过程宏
学习曲线 模式匹配 syn/quote 生态
编译速度 简单替换 需要编译 proc-macro crate
调试难度 cargo expand 可查 需要单元测试
错误信息 指向宏定义 可自定义 span
可测试性 只能测试展开结果 proc-macro2 可单元测试
能力模式匹配+替换完全控制 AST
依赖syn + quote + proc-macro2
适用场景简单 DSL、样板消除derive、attribute、复杂代码生成

9.3 性能对比

⚠️ 编译时间影响:过程宏需要编译独立的 crate,首次编译慢。但增量编译时,只有宏定义变更才重新编译。声明宏零额外编译成本。如果你的宏逻辑很简单,声明宏是更好的选择。

10. 实战:用过程宏实现 ORM 字段映射

最后,我们来实现一个完整的案例——用 Derive 过程宏自动生成 ORM 字段映射代码。这是过程宏最典型的应用场景之一。

10.1 目标

期望效果
// 用宏标注
#[derive(OrmModel)]
struct User {
    #[orm(primary_key)]
    id: i64,
    #[orm(column = "user_name")]
    name: String,
    #[orm(default)]
    email: Option<String>,
    created_at: chrono::NaiveDateTime,
}

// 自动生成:
// 1. 表名映射
// 2. 列名映射
// 3. INSERT 语句生成
// 4. SELECT 结果解析
// 5. UPDATE 语句生成

User::table_name();           // "users"
User::column_names();         // ["id", "user_name", "email", "created_at"]
User::insert_sql();           // "INSERT INTO users (id, user_name, ...) VALUES ($1, $2, ...)"
User::from_row(&row)?;        // 从 sqlx::PgRow 解析为 User

10.2 项目结构

项目结构
orm-macro/
├── orm-derive/              # 过程宏 crate
│   ├── Cargo.toml
│   └── src/lib.rs
├── orm-core/                # 运行时支持
│   ├── Cargo.toml
│   └── src/lib.rs
└── orm-tests/               # 集成测试
    ├── Cargo.toml
    └── src/main.rs

10.3 过程宏实现

orm-derive/src/lib.rs
use proc_macro::TokenStream;
use quote::{quote, format_ident};
use syn::{
    parse_macro_input, DeriveInput, Data, Fields, Attribute,
    Lit, Meta, NestedMeta, Result, Error,
};

#[proc_macro_derive(OrmModel, attributes(orm))]
pub fn derive_orm_model(input: TokenStream) -> TokenStream {
    let ast = parse_macro_input!(input as DeriveInput);
    match derive_orm_model_impl(&ast) {
        Ok(tokens) => tokens.into(),
        Err(e) => e.to_compile_error().into(),
    }
}

struct FieldInfo {
    ident: proc_macro2::Ident,
    ty: proc_macro2::Type,
    column_name: String,
    is_primary_key: bool,
    has_default: bool,
}

fn parse_orm_attrs(attrs: &[Attribute]) -> (Option<String>, bool, bool) {
    let mut column_name = None;
    let mut is_pk = false;
    let mut has_default = false;
    
    for attr in attrs.iter().filter(|a| a.path.is_ident("orm")) {
        let _ = attr.parse_nested_meta(|meta| {
            if meta.path.is_ident("primary_key") {
                is_pk = true;
            } else if meta.path.is_ident("column") {
                let v: Lit = meta.value()?.parse()?;
                if let Lit::Str(s) = v {
                    column_name = Some(s.value());
                }
            } else if meta.path.is_ident("default") {
                has_default = true;
            }
            Ok(())
        });
    }
    
    (column_name, is_pk, has_default)
}

fn derive_orm_model_impl(ast: &DeriveInput) -> Result<proc_macro2::TokenStream> {
    let struct_name = &ast.ident;
    let table_name = to_snake_case(&struct_name.to_string()) + "s";
    
    let fields = match &ast.data {
        Data::Struct(data) => match &data.fields {
            Fields::Named(ref f) => &f.named,
            _ => return Err(Error::new_spanned(ast, "Only named fields supported")),
        },
        _ => return Err(Error::new_spanned(ast, "Only structs supported")),
    };
    
    let field_infos: Vec<FieldInfo> = fields.iter().map(|f| {
        let (col, is_pk, has_default) = parse_orm_attrs(&f.attrs);
        let column_name = col.unwrap_or_else(|| to_snake_case(&f.ident.as_ref().unwrap().to_string()));
        FieldInfo {
            ident: f.ident.clone().unwrap(),
            ty: f.ty.clone(),
            column_name,
            is_primary_key: is_pk,
            has_default,
        }
    }).collect();
    
    // 表名
    let table_name_lit = &table_name;
    
    // 列名
    let col_names: Vec<_> = field_infos.iter().map(|f| &f.column_name).collect();
    
    // 字段标识
    let field_idents: Vec<_> = field_infos.iter().map(|f| &f.ident).collect();
    
    // INSERT: 非默认字段
    let insert_fields: Vec<_> = field_infos.iter()
        .filter(|f| !f.has_default)
        .collect();
    let insert_col_names: Vec<_> = insert_fields.iter().map(|f| &f.column_name).collect();
    let insert_idents: Vec<_> = insert_fields.iter().map(|f| &f.ident).collect();
    let insert_placeholders: Vec<String> = (1..=insert_fields.len())
        .map(|i| format!("${}", i))
        .collect();
    
    // UPDATE: 非主键字段
    let update_fields: Vec<_> = field_infos.iter()
        .filter(|f| !f.is_primary_key)
        .collect();
    let update_col_names: Vec<_> = update_fields.iter().map(|f| &f.column_name).collect();
    let update_idents: Vec<_> = update_fields.iter().map(|f| &f.ident).collect();
    
    // 主键
    let pk_field = field_infos.iter().find(|f| f.is_primary_key);
    let pk_ident = pk_field.map(|f| &f.ident);
    let pk_col = pk_field.map(|f| &f.column_name.as_str()).unwrap_or("id");
    
    let expanded = quote! {
        impl OrmModel for #struct_name {
            fn table_name() -> &'static str {
                #table_name_lit
            }
            
            fn column_names() -> Vec<&'static str> {
                vec![ #(#col_names),* ]
            }
            
            fn insert_sql() -> String {
                format!(
                    "INSERT INTO {} ({}) VALUES ({}) RETURNING *",
                    #table_name_lit,
                    #(#insert_col_names),*,
                    #(#insert_placeholders),*
                )
            }
            
            fn update_sql() -> String {
                let sets: Vec<String> = vec![
                    #( format!("{} = ${}", #update_col_names, 1 + {}), )*
                ];
                format!(
                    "UPDATE {} SET {} WHERE {} = $1",
                    #table_name_lit,
                    sets.join(", "),
                    #pk_col,
                )
            }
            
            fn values(&self) -> Vec<&(dyn ::sqlx::Encode<'_, ::sqlx::Postgres> + ::sqlx::Type<::sqlx::Postgres>)> {
                vec![ #( &self.#insert_idents as _ ),* ]
            }
        }
    };
    
    Ok(expanded)
}

fn to_snake_case(s: &str) -> String {
    let mut result = String::new();
    for (i, c) in s.chars().enumerate() {
        if c.is_uppercase() {
            if i > 0 { result.push('_'); }
            result.push(c.to_ascii_lowercase());
        } else {
            result.push(c);
        }
    }
    result
}

10.4 运行时 trait

orm-core/src/lib.rs
use sqlx::{FromRow, PgConnection, Postgres, QueryBuilder};

pub trait OrmModel: for<'r> FromRow<'r, sqlx::postgres::PgRow> + Sized {
    fn table_name() -> &'static str;
    fn column_names() -> Vec<&'static str>;
    fn insert_sql() -> String;
    fn update_sql() -> String;
    
    async fn insert(&self, pool: &sqlx::PgPool) -> sqlx::Result<Self> {
        let sql = Self::insert_sql();
        // 用 sqlx::query_as 执行...
        todo!()
    }
    
    async fn find_by_id(pool: &sqlx::PgPool, id: i64) -> sqlx::Result<Option<Self>> {
        let sql = format!(
            "SELECT * FROM {} WHERE id = $1",
            Self::table_name()
        );
        sqlx::query_as::<_, Self>(&sql)
            .bind(id)
            .fetch_optional(pool)
            .await
    }
    
    async fn find_all(pool: &sqlx::PgPool) -> sqlx::Result<Vec<Self>> {
        let sql = format!("SELECT * FROM {}", Self::table_name());
        sqlx::query_as::<_, Self>(&sql)
            .fetch_all(pool)
            .await
    }
}

10.5 使用效果

实际使用
use orm_core::OrmModel;
use orm_derive::OrmModel;
use sqlx::FromRow;

#[derive(OrmModel, FromRow)]
struct User {
    #[orm(primary_key)]
    id: i64,
    #[orm(column = "user_name")]
    name: String,
    #[orm(default)]
    email: Option<String>,
    created_at: chrono::NaiveDateTime,
}

async fn example(pool: &sqlx::PgPool) -> sqlx::Result<()> {
    // 查询
    let user = User::find_by_id(&pool, 1).await?;
    
    // 查询全部
    let all_users = User::find_all(&pool).await?;
    
    // 表名和列名
    println!("Table: {}", User::table_name());     // "users"
    println!("Columns: {:?}", User::column_names()); // ["id", "user_name", "email", "created_at"]
    
    Ok(())
}

💡 扩展思路:这个 ORM 宏可以继续扩展——支持 #[orm(relation = "orders")] 自动生成关联查询、支持 #[orm(index)] 自动创建迁移脚本、支持 #[orm(validate = "check_email")] 编译期验证等。过程宏的能力边界,就是你的想象力边界。

🔧 生产级 ORM 宏还需要考虑什么?

1. 错误处理:每个字段属性解析都应该有精确的错误位置和友好提示。

2. 泛型支持struct User<T> { data: T } 需要正确传递泛型参数和 where 子句。

3. 生命周期:含引用字段的结构体需要正确处理生命周期。

4. 可选字段Option<T> 字段映射为 NULL,需要特殊处理。

5. 类型映射:Rust 类型到 SQL 类型的映射表需要可扩展。

6. 异步运行时:支持 tokio / async-std 不同运行时。

7. 连接池:支持不同数据库连接池实现。