首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >38-Rust 教程 - 高级宏

38-Rust 教程 - 高级宏

作者头像
LarryLan
发布2026-06-08 14:07:18
发布2026-06-08 14:07:18
00
举报

高级宏

从 macro_rules! 到过程宏:Rust 元编程的完全体

🎬 引入

上篇我们学了 macro_rules!,你已经能用它写一些很酷的宏了。但你可能发现了一些限制:

  • 没法在宏里执行任意 Rust 代码
  • 调试起来像猜谜
  • 有些复杂的代码生成做不到

这时候,你需要 过程宏(Procedural Macros)

如果说 macro_rules! 是"模板引擎",那过程宏就是"完整的编程语言"。它允许你在编译时运行真正的 Rust 代码来生成代码。

听起来很绕?想象一下:

macro_rules! 就像一个表单模板,你填好空,它生成结果 过程宏 就像一个程序员,你告诉它需求,它现场写代码

今天,咱们就来深入 Rust 元编程的深水区。系好安全带,准备起飞!

📌 核心概念:什么是过程宏?

过程宏的三种类型

Rust 有三种过程宏:

Function-like Macros(函数式宏)

代码语言:javascript
复制
#[proc_macro]
pub fn my_macro(input: TokenStream) -> TokenStream {
    // 处理 input,生成输出
}

// 使用
my_macro!(some input);

Derive Macros(派生宏)

代码语言:javascript
复制
#[proc_macro_derive(MyTrait)]
pub fn derive_my_trait(input: TokenStream) -> TokenStream {
    // 为结构体/枚举生成 trait 实现
}

// 使用
#[derive(MyTrait)]
struct MyStruct { ... }

Attribute Macros(属性宏)

代码语言:javascript
复制
#[proc_macro_attribute]
pub fn my_attr(args: TokenStream, item: TokenStream) -> TokenStream {
    // 修改被标记的项
}

// 使用
#[my_attr]
fn my_function() { ... }

TokenStream:宏的输入输出

过程宏的核心是 TokenStream——它代表 Rust 代码的词法流。

代码语言:javascript
复制
源代码: fn add(a: i32, b: i32) -> i32 { a + b }
       ↓ 词法分析
TokenStream: [fn] [add] [(] [a] [:] [i32] [,] [b] [:] [i32] [)] [->] [i32] [{] [a] [+] [b] [}]

过程宏的工作就是:

  1. 接收 TokenStream
  2. 解析、分析、转换
  3. 输出新的 TokenStream

宏卫生(Hygiene)

宏卫生 是指宏中定义的变量不会与外部代码冲突。

代码语言:javascript
复制
// macro_rules! 有卫生保证
macro_rules! make_struct {
    ($name:ident) => {
        struct $name {
            value: i32,  // 这个 value 不会与外部冲突
        }
    };
}

// 过程宏没有自动卫生
// 需要手动使用 Span 来保证

为什么重要? 想象你写了一个宏,里面用了变量 x。如果用户代码里也有 x,就会冲突。宏卫生确保这种情况不会发生。

💻 代码示例

1. 设置过程宏项目

过程宏必须在独立的 crate 中定义:

代码语言:javascript
复制
# 创建过程宏 crate
cargo new --lib my_macros
cd my_macros

Cargo.toml:

代码语言:javascript
复制
[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 的替代品

2. 第一个过程宏:Derive Macro

让我们实现一个自定义的 #[derive(Hello)],为结构体自动生成 hello() 方法:

代码语言:javascript
复制
// 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)
}

使用这个宏:

代码语言:javascript
复制
// 主 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!
}

3. 更复杂的 Derive:自动生成 Builder 模式

代码语言:javascript
复制
// 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)
}

使用:

代码语言:javascript
复制
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);
}

4. Attribute Macro:自动添加日志

代码语言:javascript
复制
// 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)
}

使用:

代码语言:javascript
复制
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
}

5. Function-like Macro:生成路由

代码语言:javascript
复制
// 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)
}

6. 宏卫生:使用 Span

代码语言:javascript
复制
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)
}

🐛 常见坑点

坑点 1:忘记 proc-macro = true

代码语言:javascript
复制
# ❌ 错误:没有这个配置
[lib]
name = "my_macros"

# ✅ 正确
[lib]
proc-macro = true

后果: 编译错误,无法使用 #[proc_macro_derive] 等属性。

坑点 2:在过程宏 crate 中使用 std

代码语言:javascript
复制
// ❌ 错误:过程宏 crate 不能直接用 std
use std::println;

// ✅ 正确:使用 proc_macro 提供的 API
use proc_macro::TokenStream;

坑点 3:解析错误处理不当

代码语言:javascript
复制
// ❌ 错误:直接 panic
fn parse_input(input: TokenStream) -> DeriveInput {
    syn::parse(input).unwrap()  // panic!
}

// ✅ 正确:返回编译错误
fn parse_input(input: TokenStream) -> Result<DeriveInput, syn::Error> {
    syn::parse(input)
}

坑点 4:忽略 Span 导致错误位置不清

代码语言:javascript
复制
// ❌ 错误:错误没有位置信息
panic!("Only structs supported");

// ✅ 正确:指向具体代码位置
return syn::Error::new(
    input.ident.span(),
    "Only structs supported"
)
.to_compile_error()
.into();

🎯 实战案例:序列化宏

让我们实现一个简化版的 serde derive:

代码语言:javascript
复制
// 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)
}

使用:

代码语言:javascript
复制
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")}
}

🧠 思维导图

38-高级宏(过程宏)
38-高级宏(过程宏)

📝 小结

  1. 过程宏有三种类型: Function-like、Derive、Attribute
  2. 过程宏在独立的 crate 中定义,需要 proc-macro = true
  3. TokenStream 是宏的输入输出,代表 Rust 代码的词法流
  4. 核心工具链: proc-macro(标准库)、quote(生成)、syn(解析)
  5. 宏卫生保证变量不冲突,使用 Span 管理作用域
  6. 错误处理要友好,指向正确的代码位置
  7. 过程宏比 macro_rules! 更强大,但也更复杂

金句时间:

macro_rules! 是模板,过程宏是程序员——一个填空,一个写代码。

🎉 高级主题篇完结!

恭喜你完成了 Rust 高级主题四部曲:

  • ✅ 第 35 篇:unsafe Rust
  • ✅ 第 36 篇:FFI
  • ✅ 第 37 篇:宏编程
  • ✅ 第 38 篇:高级宏

这些是 Rust 中最强大也最复杂的特性。掌握它们,你就真正进入了 Rust 高手的行列!

🔗 下篇预告

高级主题学完了,接下来咱们进入 实战项目篇

从第 39 篇开始,咱们会用真实的项目来巩固前面学的知识:

  • CLI 工具开发
  • Web 服务
  • 数据库操作
  • 序列化
  • WASM 实践
  • ...

敬请期待 第 39 篇:CLI 工具开发

🔗 参考资料

  • The Rust Reference - Procedural Macros
  • Proc Macro Workshop
  • Syn crate documentation
  • Quote crate documentation
  • Writing a Skeleton Driver - 过程宏实战
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2026-06-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Larry的Hub 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 高级宏
    • 🎬 引入
    • 📌 核心概念:什么是过程宏?
      • 过程宏的三种类型
      • TokenStream:宏的输入输出
      • 宏卫生(Hygiene)
    • 💻 代码示例
      • 1. 设置过程宏项目
      • 2. 第一个过程宏:Derive Macro
      • 3. 更复杂的 Derive:自动生成 Builder 模式
      • 4. Attribute Macro:自动添加日志
      • 5. Function-like Macro:生成路由
      • 6. 宏卫生:使用 Span
    • 🐛 常见坑点
      • 坑点 1:忘记 proc-macro = true
      • 坑点 2:在过程宏 crate 中使用 std
      • 坑点 3:解析错误处理不当
      • 坑点 4:忽略 Span 导致错误位置不清
    • 🎯 实战案例:序列化宏
    • 🧠 思维导图
    • 📝 小结
    • 🎉 高级主题篇完结!
    • 🔗 下篇预告
    • 🔗 参考资料
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档