Rust:过程宏

发布于:2025-07-24 ⋅ 阅读:(20) ⋅ 点赞:(0)

个人看法,宏就是rust的灵魂和魔法。从hello world那刻开始,rust的世界就永远无法离开宏。

宏并不是装B的利器,但如果你喜欢装,宏一定不可以不玩。你可以不用,但你不能不懂。对rust人来讲,不理解宏,定会少了一份亲切和挚爱。

虽然声明宏相对比较简单(不是本次的话题),但是rust的过程宏,却看起来有点复杂,容易上头。

如果从定义慢慢展开,线索自动就浮出水面。

说明:以下内容得到doubao的技术支持,用AI来学习编程效率是非常高的。

一、推荐阅读

1、文章
Rust 中的过程宏 proc-macro,来自知乎

[如果你是颜控粉,下面链接的文章排版非常精美!强烈推荐]

Rust 中的过程宏 proc-macro,来自作者博客

注:上面两个链接都是同一个作者的向一篇文章。

2、syn和quote

毫无疑问,rust宏需要用到,syn和quote库来助力。简单可以参考:

rust 使用 syn 库解析 AST
Rust Crate 使用 :Quote

二、 解析库syn 及DeriveInput

看完上面的文章,你必定还有一些疑问。没关系,因为你要提前了解一下解析库syn,特别是:

1、DeriveInput

pub struct DeriveInput {
     pub attrs: Vec<Attribute>,
     pub vis: Visibility,
     pub ident: Ident,
     pub generics: Generics,
     pub data: Data,
 }

在 syn 库中,以上DeriveInput 是一个用于表示 #[derive(…)] 宏输入的结构体。当你编写自定义派生宏时,DeriveInput 是解析输入类型定义的核心结构。

从上面的代码可以看出,DeriveInput 结构体包含了被派生宏应用的类型定义的完整信息,主要包括:
1、属性(Attributes):类型上的所有属性(如#[derive(Debug)]、#[cfg(…)] 等)。
2、可见性(Visibility):类型的可见性修饰符(如 pub)。
3、标识符(Ident):类型的名称(如结构体名、枚举名)。
4、数据类型(Data):类型的具体形式,主要有3种形式:
5、泛型参数(Generics):类型的泛型参数、约束和 where子句。

比如,对于以下结构体定义,

#[derive(MyMacro)]
pub struct Point<T> {
    x: T,
    y: T,
}

其对应的 DeriveInput 结构(简化表示)如下:

DeriveInput {
    attrs: [/* #[derive(MyMacro)] */],
    vis: Visibility::Public,
    ident: Ident("Point"),
    generics: Generics {
        params: [/* 泛型参数 T */],
        where_clause: None,
    },
    data: Data::Struct(
        DataStruct {
            fields: Fields::Named(
                FieldsNamed {
                    named: [
                        Field { ident: Some(Ident("x")), ty: TypePath("T") },
                        Field { ident: Some(Ident("y")), ty: TypePath("T") },
                    ],
                }
            ),
        }
    ),
}

三、DeriveInput各个字段

现在就可以更好了解DeriveInput中的各个字段及用法。

(一)Attributes
Attribute 类型用于表示 Rust 代码中的属性(Attributes),例如#[derive(Debug)]、#[cfg(test)]、#[allow(unused)] 等。
属性是 Rust 元编程的重要组成部分,可用于注解类型、函数、字段等元素,为编译器或工具提供额外信息。

以下是关于 Attribute 的详细用法:

  1. Attribute 概述

属性在 Rust 代码中以 #[meta] 或 #![meta](内部属性)的形式存在,syn 库将其解析为 Attribute 结构体:

pub struct Attribute {
    pub pound_token: token::Pound,  // # 符号
    pub bracket_token: token::Bracket,  // [] 括号
    pub meta: Meta,  // 属性的元数据内容
}

其中,Meta 是一个枚举类型,表示属性的不同形态:

pub enum Meta {
    Word(Ident),                // 简单单词属性,如 #[test]
    List(MetaList),             // 列表属性,如 #[derive(Debug)]
    NameValue(MetaNameValue),   // 键值对属性,如 #[cfg(target_os = "linux")]
}

其中MetaList:

pub struct MetaList {
    pub path: Path,                  // 属性路径(如 derive、cfg)
    pub paren_token: token::Paren,   // 括号 `()`
    pub nested: Punctuated<NestedMeta, Comma>,  // 嵌套的元数据列表
}

进一步Path:

pub struct Path {
    /// 路径的全局标记(是否以 `::` 开头,如 `::std::io`)。
    pub leading_colon: Option<Token![::]>,
    /// 路径中的各个 segment(段),例如 `std`、`io`、`Read` 分别是 `std::io::Read` 的三个段。
    pub segments: Punctuated<PathSegment, Token![::]>,
}

和MetaNameValue:

pub struct MetaNameValue {
    pub path: Path,         // 属性路径(键,如 target_os、default)
    pub eq_token: Token![=], // 等号 `=`
    pub lit: Lit,           // 字面量值(如字符串、整数、布尔值)
}
  1. 解析属性示例

以下示例展示如何解析不同类型的属性:

use syn::{parse_str, Attribute, Meta, MetaList, MetaNameValue};

fn main() -> syn::Result<()> {
    // 解析简单单词属性 #[test]
    let attr_test: Attribute = parse_str("#[test]")?;
    match attr_test.meta {
        Meta::Word(ident) => {
            assert_eq!(ident.to_string(), "test");
        }
        _ => panic!("Expected Word"),
    }

    // 解析列表属性 #[derive(Debug, Clone)]
    let attr_derive: Attribute = parse_str("#[derive(Debug, Clone)]")?;
    if let Meta::List(list) = attr_derive.meta {
        assert_eq!(list.path.get_ident().unwrap().to_string(), "derive");
        
        // 遍历列表中的嵌套元数据
        for nested_meta in list.nested {
            if let syn::NestedMeta::Meta(meta) = nested_meta {
                if let Meta::Word(ident) = meta {
                    println!("derive 项: {}", ident); // 输出: Debug, Clone
                }
            }
        }
    }

    // 解析键值对属性 #[cfg(target_os = "linux")]
    let attr_cfg: Attribute = parse_str("#[cfg(target_os = \"linux\")]")?;
    if let Meta::NameValue(nv) = attr_cfg.meta {
        assert_eq!(nv.path.get_ident().unwrap().to_string(), "cfg");
        
        if let syn::Lit::Str(lit_str) = nv.lit {
            assert_eq!(lit_str.value(), "linux");
        }
    }

    Ok(())
}
  1. 在 AST 中访问属性

在解析 Rust 类型定义时,Attribute 通常与其他元素(如结构体、字段、函数)关联。以下示例展示如何访问结构体及其字段的属性:

use syn::{parse_quote, DeriveInput, Data, Fields};

fn main() {
    let input: DeriveInput = parse_quote! {
        #[derive(Debug)]
        #[cfg_attr(test, derive(PartialEq))]
        struct User {
            #[serde(rename = "username")]
            name: String,
            
            #[allow(dead_code)]
            age: u32,
        }
    };

    // 访问结构体属性
    for attr in &input.attrs {
        if let Ok(Meta::List(meta_list)) = attr.parse_meta() {
            if meta_list.path.is_ident("derive") {
                println!("结构体实现: {:?}", meta_list);
            }
        }
    }

    // 访问字段属性
    if let Data::Struct(data_struct) = &input.data {
        if let Fields::Named(fields) = &data_struct.fields {
            for field in &fields.named {
                for attr in &field.attrs {
                    if let Ok(Meta::NameValue(nv)) = attr.parse_meta() {
                        if nv.path.is_ident("serde") {
                            println!("字段 {} 的 serde 属性: {:?}", 
                                field.ident.as_ref().unwrap(), nv);
                        }
                    }
                }
            }
        }
    }
}
  1. 创建和生成属性

在过程宏中,有时需要动态生成属性。以下示例展示如何使用 quote 宏生成属性:

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

#[proc_macro_derive(MyAttr)]
pub fn my_attr_derive(input: TokenStream) -> TokenStream {
    let DeriveInput { ident, .. } = parse_macro_input!(input);

    // 生成一个 #[cfg(test)] 属性
    let test_attr = quote! {
        #[cfg(test)]
        mod tests {
            use super::#ident;

            #[test]
            fn test_new() {
                let _ = #ident::new();
            }
        }
    };

    TokenStream::from(test_attr)
}
  1. 常用属性处理模式

(1) 检查特定属性是否存在

fn has_derive_debug(attrs: &[Attribute]) -> bool {
    attrs.iter().any(|attr| {
        attr.path.is_ident("derive") && 
        matches!(attr.parse_meta(), Ok(Meta::List(list)) if 
            list.nested.iter().any(|nested| {
                if let syn::NestedMeta::Meta(Meta::Word(ident)) = nested {
                    ident == "Debug"
                } else {
                    false
                }
            })
        )
    })
}

(2) 提取属性参数

fn get_serde_rename(attrs: &[Attribute]) -> Option<String> {
    attrs.iter()
        .find(|attr| attr.path.is_ident("serde"))
        .and_then(|attr| {
            if let Ok(Meta::List(list)) = attr.parse_meta() {
                list.nested.iter()
                    .find_map(|nested| {
                        if let syn::NestedMeta::Meta(Meta::NameValue(nv)) = nested {
                            if nv.path.is_ident("rename") {
                                if let syn::Lit::Str(s) = &nv.lit {
                                    return Some(s.value());
                                }
                            }
                        }
                        None
                    })
            } else {
                None
            }
        })
}
  1. 注意事项

属性位置:属性可以应用于不同的 Rust 元素(如模块、类型、函数、字段等),解析时需注意上下文。
内部属性:以 #! 开头的属性(如#![cfg_attr])作用于包含它的项,解析方式相同。
属性嵌套:属性值可以嵌套(如 #[cfg(any(unix, windows))]),需递归解析。
过程宏中的属性:在自定义 derive 宏中,通常需要处理用户提供的属性并生成新的属性。

总结

Attribute 是 syn 库中处理 Rust 属性的核心类型,主要用于: 解析和识别代码中的各种属性(单词、列表、键值对) 在 AST遍历中访问类型、函数、字段等元素的属性 在过程宏中生成新的属性或处理现有属性 掌握 Attribute 的用法对于构建自定义 derive宏、代码分析工具和代码转换工具至关重要。

(二)Visibility
Visibility 类型用于表示代码元素(如结构体、字段、函数等)的可见性修饰符(如 pub、pub(crate)、pub(super) 等)。可见性是 Rust 模块系统的核心特性,syn 提供了完整的类型系统来解析和操作这些修饰符。

以下是关于 Visibility 的详细用法:

  1. Visibility 概述
    Visibility 是一个枚举类型,表示 Rust 中不同级别的可见性:
pub enum Visibility {
    Inherited,            // 继承可见性(默认,无 pub 修饰符)
    Public(Token![pub]),  // 公共可见性(pub)
    Restricted(Restricted), // 受限可见性(如 pub(crate)pub(super)pub(in path))
    CfgPrivate,           // 条件私有可见性(#![cfg(...)] 内部使用)
}

其中,Restricted 结构体表示带路径的可见性:

pub struct Restricted {
    pub pub_token: Token![pub],      // pub 关键字
    pub paren_token: token::Paren,   // 括号
    pub path: Path,                  // 路径(如 crate、super、module::submodule)
}
  1. 解析不同类型的可见性

以下示例展示如何解析各种可见性修饰符:

use syn::{parse_str, Visibility};

fn main() -> syn::Result<()> {
    // 1. 解析 pub 可见性
    let pub_vis: Visibility = parse_str("pub")?;
    match pub_vis {
        Visibility::Public(_) => println!("公共可见性"),
        _ => panic!("期望 pub"),
    }

    // 2. 解析 pub(crate) 可见性
    let crate_vis: Visibility = parse_str("pub(crate)")?;
    if let Visibility::Restricted(restricted) = crate_vis {
        assert_eq!(restricted.path.segments[0].ident.to_string(), "crate");
        println!("crate 可见性");
    }

    // 3. 解析 pub(in module::submodule) 可见性
    let in_vis: Visibility = parse_str("pub(in my_module::sub)")?;
    if let Visibility::Restricted(restricted) = in_vis {
        let path_str = restricted.path.segments.iter()
            .map(|seg| seg.ident.to_string())
            .collect::<Vec<_>>()
            .join("::");
        assert_eq!(path_str, "my_module::sub");
        println!("限制在 my_module::sub 可见");
    }

    // 4. 默认(继承)可见性
    let default_vis: Visibility = parse_str("")?; // 无 pub 修饰符
    match default_vis {
        Visibility::Inherited => println!("继承可见性"),
        _ => panic!("期望继承可见性"),
    }

    Ok(())
}
  1. 在 AST 中访问元素的可见性

在解析 Rust 类型定义时,可以访问结构体、字段、函数等元素的可见性:

use syn::{parse_quote, DeriveInput, Data, Fields};

fn main() {
    let input: DeriveInput = parse_quote! {
        pub(crate) struct MyStruct {
            pub field1: i32,
            field2: String,
            pub(in crate::module) field3: bool,
        }
    };

    // 1. 访问结构体的可见性
    match input.vis {
        syn::Visibility::Restricted(restricted) => {
            assert_eq!(restricted.path.segments[0].ident.to_string(), "crate");
            println!("结构体可见性: pub(crate)");
        }
        _ => panic!("期望 restricted 可见性"),
    }

    // 2. 访问字段的可见性
    if let Data::Struct(data_struct) = input.data {
        if let Fields::Named(fields) = data_struct.fields {
            for field in fields.named {
                let field_name = field.ident.as_ref().unwrap().to_string();
                
                match field.vis {
                    syn::Visibility::Public(_) => {
                        println!("字段 {} 可见性: pub", field_name);
                    }
                    syn::Visibility::Restricted(restricted) => {
                        let path_str = restricted.path.segments.iter()
                            .map(|seg| seg.ident.to_string())
                            .collect::<Vec<_>>()
                            .join("::");
                        println!("字段 {} 可见性: pub(in {})", field_name, path_str);
                    }
                    syn::Visibility::Inherited => {
                        println!("字段 {} 可见性: 私有", field_name);
                    }
                    _ => {}
                }
            }
        }
    }
}
  1. 创建和生成可见性

在过程宏中,有时需要动态生成可见性修饰符。以下示例展示如何使用 quote 宏生成具有特定可见性的代码:

use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput, Visibility};

#[proc_macro_derive(MyDerive)]
pub fn my_derive_derive(input: TokenStream) -> TokenStream {
    let DeriveInput { ident, vis, .. } = parse_macro_input!(input);

    // 根据原类型的可见性生成关联函数
    let output_vis = match vis {
        Visibility::Public(_) => quote!(pub),
        Visibility::Restricted(restricted) => {
            // 复制原有的受限可见性
            let path = &restricted.path;
            quote!(pub(#path))
        }
        _ => quote!(), // 默认不添加可见性
    };

    let expanded = quote! {
        impl #ident {
            #output_vis fn new() -> Self {
                Self {}
            }
        }
    };

    TokenStream::from(expanded)
}
  1. 判断可见性的实用方法
    以下是一些判断和处理可见性的实用函数:
use syn::{Visibility, Path};

/// 判断是否为公共可见性(pub)
fn is_public(vis: &Visibility) -> bool {
    matches!(vis, Visibility::Public(_))
}

/// 判断是否为 crate 可见性(pub(crate))
fn is_crate_public(vis: &Visibility) -> bool {
    if let Visibility::Restricted(restricted) = vis {
        if restricted.path.segments.len() == 1 {
            let first_segment = &restricted.path.segments[0];
            return first_segment.ident == "crate" && first_segment.arguments.is_empty();
        }
    }
    false
}

/// 获取受限可见性的路径字符串
fn get_restricted_path(vis: &Visibility) -> Option<String> {
    if let Visibility::Restricted(restricted) = vis {
        Some(restricted.path.segments.iter()
            .map(|seg| seg.ident.to_string())
            .collect::<Vec<_>>()
            .join("::"))
    } else {
        None
    }
}
  1. 注意事项

默认可见性:未显式标记 pub 的元素具有 Inherited 可见性,通常表示私有(private)。
路径解析:受限可见性的路径(如pub(in module::sub))需要递归解析 Path 类型。
过程宏中的可见性:在生成代码时,通常应保留原类型的可见性(如示例中的my_derive_derive)。
CfgPrivate 的使用:这是一种特殊的可见性,主要用于 #[cfg(…)]内部,实际开发中很少直接处理。

总结

Visibility 是 syn 库中处理 Rust 可见性修饰符的核心类型,主要用于:
解析和识别代码中的各种可见性修饰符(pub、pub(crate) 等) 在 AST 遍历中访问类型、函数、字段等元素的可见性在过程宏中生成新的可见性或保留原有可见性 掌握 Visibility 的用法对于构建自定义 derive宏、代码分析工具和代码转换工具至关重要。

(三)Ident

Ident(标识符)是解析和操作 Rust 代码时最基础且常用的类型之一。它表示代码中的名称(如变量名、函数名、结构体名等),并提供了一系列实用方法。

以下是关于 Ident 的详细用法:

  1. Ident 概述

Ident 是 syn 中表示标识符的类型,定义如下:

pub struct Ident {
    pub span: Span,     // 标识符在源代码中的位置
    pub sym: Symbol,    // 标识符的字符串内容
}

主要用于表示:

变量名(如 x、my_variable)
类型名(如 String、MyStruct)
函数名(如 main、process_data)
模块名、路径等

  1. 创建 Ident 的方法

(1) 从字符串解析

使用 parse_str 或 parse_quote:

use syn::{parse_str, Ident};

fn main() -> syn::Result<()> {
    // 直接解析字符串
    let ident: Ident = parse_str("my_function")?;
    println!("标识符: {}", ident); // 输出: my_function

    // 使用 quote 宏创建
    let ident_from_quote: Ident = syn::parse_quote!(another_ident);
    println!("从 quote 创建: {}", ident_from_quote); // 输出: another_ident
    
    Ok(())
}

(2) 在过程宏中创建

在自定义 derive 宏或过程宏中,通常从输入参数获取 Ident:

use proc_macro::TokenStream;
use syn::{parse_macro_input, DeriveInput};

#[proc_macro_derive(MyTrait)]
pub fn my_trait_derive(input: TokenStream) -> TokenStream {
    let DeriveInput { ident, .. } = parse_macro_input!(input);
    println!("类型名称: {}", ident); // 例如: MyStruct
    // ...
}
  1. Ident 的核心方法

(1) 获取标识符的字符串内容

let ident: Ident = parse_str("username")?;
let name: &str = ident.to_string(); // "username"

(2) 比较标识符

let id1: Ident = parse_str("foo")?;
let id2: Ident = parse_str("bar")?;

if id1 == "foo" {
    println!("匹配成功");
}

// 比较两个 Ident 实例
if id1 != id2 {
    println!("标识符不同");
}

(3) 修改标识符

use quote::format_ident;

let original: Ident = parse_str("user")?;
let modified = format_ident!("{}_name", original); // user_name

(4) 获取源代码位置

let ident: Ident = parse_str("main").unwrap();
let span = ident.span(); // 表示 "main" 在源代码中的位置
  1. 在 AST 遍历中使用 Ident

在解析复杂结构(如结构体、枚举)时,Ident 常用于访问字段名、变体名等:

use syn::{parse_quote, Data, DeriveInput, Fields};

fn main() {
    let input: DeriveInput = parse_quote! {
        struct User {
            username: String,
            age: u32,
        }
    };

    if let Data::Struct(data_struct) = input.data {
        if let Fields::Named(fields) = data_struct.fields {
            for field in fields.named {
                let field_name: &Ident = field.ident.as_ref().unwrap();
                println!("字段名: {}", field_name);
            }
        }
    }
}
  1. 在过程宏中生成 Ident

在自定义 derive 宏中,常需要根据输入生成新的标识符:

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

#[proc_macro_derive(Builder)]
pub fn builder_derive(input: TokenStream) -> TokenStream {
    let DeriveInput { ident, .. } = parse_macro_input!(input);
    
    // 生成关联的 Builder 类型名
    let builder_ident = format_ident!("{}Builder", ident);
    
    let expanded = quote! {
        pub struct #builder_ident {
            // ...
        }
        
        impl #ident {
            pub fn builder() -> #builder_ident {
                // ...
            }
        }
    };
    
    TokenStream::from(expanded)
}
  1. 注意事项

大小写敏感:Ident 区分大小写(如 User 和 user 是不同的标识符)。
生命周期:Ident本身拥有其内容,避免在需要引用的场景中直接使用。
关键字处理:如果标识符是 Rust 关键字(如 fn、let),需使用反引号包裹(如fn)。

总结

Ident 是 syn 库中处理 Rust 代码标识符的核心类型,
主要用于: 解析和创建代码中的名称 在 AST 遍历中访问和操作标识符在过程宏中生成新的标识符名称 掌握 Ident 的用法是构建 Rust 代码分析工具、自定义 derive 宏和代码生成器的基础。

(四)Data的结构

在 syn 库中,Data 是一个核心枚举类型,用于表示被解析的类型定义(如结构体、枚举、联合)的具体数据结构。

Data 枚举有三个变体,对应 Rust 中的三种主要复合类型:

pub enum Data {
    Struct(DataStruct),    // 结构体
    Enum(DataEnum),        // 枚举
    Union(DataUnion),      // 联合(Rust 中较少使用)
}
  1. 解析结构体(DataStruct)

结构体可以是以下三种形式之一:
具名字段(Named Fields):struct Point { x: i32, y: i32 }
元组结构体(Tuple Fields):struct Point(i32, i32)
单元结构体(Unit Struct):struct Point;

举例代码

use syn::{parse_quote, DeriveInput, Data, Fields};

let input: DeriveInput = parse_quote! {
    struct Point {
        x: i32,
        y: i32,
    }
};

match input.data {
    Data::Struct(data_struct) => {
        match data_struct.fields {
            Fields::Named(fields) => {
                // 处理具名字段
                for field in fields.named {
                    let name = field.ident.unwrap(); // 字段名(x, y)
                    let ty = field.ty;               // 字段类型(i32)
                    println!("Field: {}: {}", name, quote::quote! {#ty});
                }
            }
            Fields::Unnamed(fields) => {
                // 处理元组字段
                for (i, field) in fields.unnamed.iter().enumerate() {
                    let ty = &field.ty;
                    println!("Tuple field {}: {}", i, quote::quote! {#ty});
                }
            }
            Fields::Unit => {
                // 处理单元结构体
                println!("Unit struct has no fields");
            }
        }
    }
    Data::Enum(_) | Data::Union(_) => unreachable!(),
}

关于DataStruct:

DataStruct 类型用于表示结构体定义。它是解析和操作 Rust 结构体的核心结构,包含结构体的字段、可见性、属性等信息。

以下是关于 DataStruct 的详细定义和用法:

  1. DataStruct 的定义结构

DataStruct 结构体表示一个完整的结构体定义,具体如下:

pub struct DataStruct {
    pub struct_token: Token![struct],  // struct 关键字
    pub fields: Fields,                // 结构体字段
    pub semi_token: Option<Token![;]>, // 分号(用于单元结构体或元组结构体)
}

其中Fields的定义如下:

pub enum Fields {
    Named(FieldsNamed),       // 命名字段结构体(如 struct Point { x: i32, y: i32 }Unnamed(FieldsUnnamed),   // 元组结构体(如 struct Pair(i32, i32))
    Unit,                     // 单元结构体(如 struct Unit;}

而FieldsNamed 和 FieldsUnnamed 分别表示命名字段和元组字段:

pub struct FieldsNamed {
    pub brace_token: token::Brace,  // 花括号 {}
    pub named: Punctuated<Field, Comma>,  // 命名字段列表
}

pub struct FieldsUnnamed {
    pub paren_token: token::Paren,  // 圆括号 ()
    pub unnamed: Punctuated<Field, Comma>,  // 元组字段列表
}

pub struct Field {
    pub attrs: Vec<Attribute>,     // 字段属性(如 #[serde(skip)])
    pub vis: Visibility,           // 字段可见性
    pub ident: Option<Ident>,      // 字段名(命名字段有名称,元组字段无名称)
    pub colon_token: Option<Token![:]>,  // 冒号(命名字段有冒号)
    pub ty: Type,                  // 字段类型
}
  1. 解析不同类型的结构体

以下示例展示如何解析不同类型的结构体:

use syn::{parse_str, DataStruct, Fields};

fn main() -> syn::Result<()> {
    // 1. 解析命名字段结构体
    let named_struct: DataStruct = parse_str("struct Point { x: i32, y: i32 }")?;
    match named_struct.fields {
        Fields::Named(fields) => {
            println!("命名字段结构体:");
            for field in fields.named {
                let name = field.ident.unwrap();
                let ty = field.ty;
                println!("  字段 {}: {:?}", name, ty);
            }
        }
        _ => panic!("期望命名字段结构体"),
    }

    // 2. 解析元组结构体
    let tuple_struct: DataStruct = parse_str("struct Pair(i32, String)")?;
    match tuple_struct.fields {
        Fields::Unnamed(fields) => {
            println!("元组结构体:");
            for (i, field) in fields.unnamed.into_iter().enumerate() {
                println!("  字段 {}: {:?}", i, field.ty);
            }
        }
        _ => panic!("期望元组结构体"),
    }

    // 3. 解析单元结构体
    let unit_struct: DataStruct = parse_str("struct Unit;")?;
    match unit_struct.fields {
        Fields::Unit => {
            println!("单元结构体");
        }
        _ => panic!("期望单元结构体"),
    }

    Ok(())
}
  1. 在 AST 中识别和处理结构体

在解析更复杂的 Rust 代码时,通常需要遍历 AST 并识别结构体定义:

use syn::{parse_quote, Item};

fn main() {
    let module: syn::File = parse_quote! {
        mod my_module {
            #[derive(Debug)]
            pub struct User {
                name: String,
                age: u32,
            }
            
            struct Pair(i32, i32);
            
            struct Unit;
        }
    };

    // 遍历模块中的所有项
    for item in module.items {
        if let Item::Struct(struct_item) = item {
            println!("找到结构体: {}", struct_item.ident);
            
            // 检查结构体属性
            let has_debug = struct_item.attrs.iter()
                .any(|attr| attr.path.is_ident("derive") && 
                    attr.tokens.to_string().contains("Debug"));
            
            if has_debug {
                println!("  实现了 Debug trait");
            }
            
            // 处理结构体字段
            match &struct_item.data {
                syn::Data::Struct(data_struct) => {
                    match &data_struct.fields {
                        Fields::Named(fields) => {
                            println!("  命名字段:");
                            for field in &fields.named {
                                let name = field.ident.as_ref().unwrap();
                                println!("    {}: {:?}", name, field.ty);
                            }
                        }
                        Fields::Unnamed(fields) => {
                            println!("  元组字段:");
                            for (i, field) in fields.unnamed.iter().enumerate() {
                                println!("    字段 {}: {:?}", i, field.ty);
                            }
                        }
                        Fields::Unit => {
                            println!("  单元结构体");
                        }
                    }
                }
                _ => unreachable!(),
            }
        }
    }
}

上面用到了Item,从Item的定义可以得到清晰的理解:

pub enum Item {
    /// 模块定义(mod my_module { ... })
    Mod(ItemMod),
    /// 函数定义(fn add(a: i32, b: i32) -> i32 { ... })
    Fn(ItemFn),
    /// 结构体定义(struct Point { x: i32, y: i32 })
    Struct(ItemStruct),
    /// 枚举定义(enum Color { Red, Green, Blue })
    Enum(ItemEnum),
    /// trait 定义(trait MyTrait { ... })
    Trait(ItemTrait),
    /// impl 块(impl MyTrait for MyType { ... }Impl(ItemImpl),
    /// 常量定义(const PI: f64 = 3.14159;Const(ItemConst),
    /// 静态变量(static COUNT: u32 = 0;Static(ItemStatic),
    /// 类型别名(type Result<T> = std::result::Result<T, Error>;Type(ItemType),
    /// 外部 crate 声明(extern crate alloc;ExternCrate(ItemExternCrate),
    /// 使用声明(use std::io;)
    Use(ItemUse),
    /// 宏定义(macro_rules! my_macro { ... })
    Macro(ItemMacro),
    /// 外部块(extern "C" { ... }ExternBlock(ItemExternBlock),
    // 其他变体...
}
  1. 修改和生成结构体

在过程宏中,常需要动态生成或修改结构体。以下示例展示如何使用 quote 宏生成新的结构体:

use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput, Data, Fields};

#[proc_macro_derive(Builder)]
pub fn builder_derive(input: TokenStream) -> TokenStream {
    let DeriveInput { ident, data, .. } = parse_macro_input!(input as DeriveInput);

    // 确保输入是结构体
    let fields = match data {
        Data::Struct(data_struct) => match data_struct.fields {
            Fields::Named(fields) => fields.named,
            _ => panic!("Builder 只能用于命名字段结构体"),
        },
        _ => panic!("Builder 只能用于结构体"),
    };

    // 生成构建器结构体名
    let builder_ident = format_ident!("{}Builder", ident);

    // 为每个字段生成构建器方法
    let builder_fields = fields.iter().map(|field| {
        let name = field.ident.as_ref().unwrap();
        let ty = &field.ty;
        quote! {
            #name: Option<#ty>,
        }
    });

    let builder_methods = fields.iter().map(|field| {
        let name = field.ident.as_ref().unwrap();
        let ty = &field.ty;
        quote! {
            pub fn #name(&mut self, #name: #ty) -> &mut Self {
                self.#name = Some(#name);
                self
            }
        }
    });

    let build_method_fields = fields.iter().map(|field| {
        let name = field.ident.as_ref().unwrap();
        quote! {
            #name: self.#name.take().unwrap_or_else(|| panic!("字段 `{}` 未设置", stringify!(#name))),
        }
    });

    // 生成构建器代码
    let expanded = quote! {
        pub struct #builder_ident {
            #(#builder_fields)*
        }

        impl #ident {
            pub fn builder() -> #builder_ident {
                #builder_ident {
                    #(#fields: None,)*
                }
            }
        }

        impl #builder_ident {
            #(#builder_methods)*

            pub fn build(&mut self) -> #ident {
                #ident {
                    #(#build_method_fields)*
                }
            }
        }
    };

    TokenStream::from(expanded)
}
  1. 实用方法与注意事项

(1) 获取结构体字段数量

fn field_count(data_struct: &DataStruct) -> usize {
    match &data_struct.fields {
        Fields::Named(fields) => fields.named.len(),
        Fields::Unnamed(fields) => fields.unnamed.len(),
        Fields::Unit => 0,
    }
}

(2) 检查结构体是否有特定属性

fn has_attribute(struct_item: &syn::ItemStruct, attr_name: &str) -> bool {
    struct_item.attrs.iter().any(|attr| attr.path.is_ident(attr_name))
}

(3) 注意事项

字段类型:元组结构体的字段没有名称,命名字段结构体的字段有名称。 属性处理:结构体和字段的属性(如 #[derive]、#[serde])可能影响后续处理。
生命周期与泛型:结构体的泛型参数和生命周期参数存储在 ItemStruct.generics 中。

总结

DataStruct 是 syn 库中处理 Rust 结构体定义的核心类型,主要用于:
解析和识别代码中的结构体结构(包括字段、属性、可见性等)
在 AST 遍历中访问和处理结构体
在过程宏中生成或修改结构体 掌握
DataStruct 的用法对于构建自定义 derive 宏、代码分析工具和代码转换工具至关重要。

  1. 解析枚举(DataEnum)

枚举由多个变体(Variants)组成,每个变体可以有不同的字段类型:
无字段:variantA
元组字段:variantB(i32,String)
具名字段:variantC { x: i32, y: String }

举例代码

use syn::{parse_quote, DeriveInput, Data, Fields};

let input: DeriveInput = parse_quote! {
    enum Message {
        Quit,
        Move { x: i32, y: i32 },
        Write(String),
    }
};

match input.data {
    Data::Enum(data_enum) => {
        for variant in data_enum.variants {
            let variant_name = &variant.ident;
            println!("Variant: {}", variant_name);
            
            match variant.fields {
                Fields::Named(fields) => {
                    // 处理具名字段变体(如 Move)
                    println!("  Named fields:");
                    for field in fields.named {
                        let name = field.ident.unwrap();
                        let ty = &field.ty;
                        println!("    {}: {}", name, quote::quote! {#ty});
                    }
                }
                Fields::Unnamed(fields) => {
                    // 处理元组字段变体(如 Write)
                    println!("  Tuple fields:");
                    for (i, field) in fields.unnamed.iter().enumerate() {
                        let ty = &field.ty;
                        println!("    Field {}: {}", i, quote::quote! {#ty});
                    }
                }
                Fields::Unit => {
                    // 处理无字段变体(如 Quit)
                    println!("  Unit variant (no fields)");
                }
            }
        }
    }
    _ => unreachable!(),
}
  1. 解析联合(DataUnion)

联合在 Rust 中较少使用,类似于 C 语言中的 union,所有字段共享同一块内存。
举例代码

use syn::{parse_quote, DeriveInput, Data};

let input: DeriveInput = parse_quote! {
    union MyUnion {
        int: i32,
        float: f32,
        pointer: *mut u8,
    }
};

match input.data {
    Data::Union(data_union) => {
        println!("Union fields:");
        for field in data_union.fields.named {
            let name = field.ident.unwrap();
            let ty = &field.ty;
            println!("  {}: {}", name, quote::quote! {#ty});
        }
    }
    _ => unreachable!(),
}
  1. 在派生宏中处理 Data

以下是一个完整的派生宏示例,展示如何根据不同的 Data 类型生成不同的代码:

use proc_macro::TokenStream;
use syn::{parse_macro_input, DeriveInput, Data, Fields};
use quote::quote;

#[proc_macro_derive(MyMacro)]
pub fn derive_my_macro(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let name = &input.ident;
    
    let impl_block = match input.data {
        Data::Struct(data_struct) => {
            match data_struct.fields {
                Fields::Named(fields) => {
                    // 处理具名字段结构体
                    let field_names = fields.named.iter().map(|f| {
                        f.ident.as_ref().unwrap()
                    });
                    
                    quote! {
                        fn print_fields(&self) {
                            println!("Fields of {}:", stringify!(#name));
                            #(println!("  {}: {:?}", stringify!(#field_names), self.#field_names);)*
                        }
                    }
                }
                Fields::Unnamed(fields) => {
                    // 处理元组结构体
                    let field_count = fields.unnamed.len();
                    
                    quote! {
                        fn print_fields(&self) {
                            println!("Tuple struct {} with {} fields", stringify!(#name), #field_count);
                        }
                    }
                }
                Fields::Unit => {
                    // 处理单元结构体
                    quote! {
                        fn print_fields(&self) {
                            println!("Unit struct {}", stringify!(#name));
                        }
                    }
                }
            }
        }
        Data::Enum(data_enum) => {
            // 处理枚举
            let variant_names = data_enum.variants.iter().map(|v| &v.ident);
            
            quote! {
                fn list_variants() {
                    println!("Variants of {}:", stringify!(#name));
                    #(println!("  {}", stringify!(#variant_names));)*
                }
            }
        }
        Data::Union(_) => {
            // 处理联合
            quote! {
                fn size_in_bytes() -> usize {
                    std::mem::size_of::<Self>()
                }
            }
        }
    };
    
    let expanded = quote! {
        impl #name {
            #impl_block
        }
    };
    
    TokenStream::from(expanded)
}

总结

Data 枚举:区分结构体、枚举和联合三种类型定义。 结构体解析:处理具名、元组和单元结构体的不同字段布局。
枚举解析:遍历变体并处理每个变体的字段类型。 联合解析:访问联合的所有字段(共享内存)。
通过模式匹配和递归遍历,你可以在自定义派生宏中根据类型定义的具体结构生成精确的代码。这是构建复杂宏(如 serde 的序列化 /
反序列化)的基础。

(五)Generics 的结构

Generics 结构体主要包含以下字段:

  1. params:泛型参数列表,可能包含:
    类型参数(如 T) 生命周期参数(如 'a) 常量参数(如 N: usize)
  2. where_clause:可选的 where 子句,用于指定额外的约束条件。
  3. lt_token 和gt_token:泛型参数列表的左右尖括号(< 和 >)。

如下结构体

#[derive(MyMacro)]
pub struct Point<T, U: Clone + 'static>
where
    T: Debug + Copy,
{
    x: T,
    y: U,
}

对应的 Generics 结构(简化表示)如下:

Generics {
    params: [
        // 类型参数 T
        GenericParam::Type(
            TypeParam {
                ident: Ident("T"),
                bounds: [/* TraitBound(Debug), TraitBound(Copy) */],
                // 其他字段...
            }
        ),
        // 类型参数 U 及其约束
        GenericParam::Type(
            TypeParam {
                ident: Ident("U"),
                bounds: [/* TraitBound(Clone) */],
                lifetime_bounds: [/* Lifetime('static) */],
                // 其他字段...
            }
        ),
    ],
    where_clause: Some(
        WhereClause {
            predicates: [
                // where T: Debug + Copy
                WherePredicate::Type(
                    WhereTypePredicate {
                        bounded_ty: TypePath("T"),
                        bounds: [/* TraitBound(Debug), TraitBound(Copy) */],
                    }
                )
            ]
        }
    )
}

关键方法解析

  1. split_for_impl():

这是 Generics 最常用的方法,返回一个三元组(impl_generics,ty_generics,where_clause):
impl_generics:用于 impl 块的泛型参数(如 <T, U>)。
ty_generics:用于类型名后的泛型参数(如 <T,U> 或空)。
where_clause:where 子句(如 where T: Debug)。

  1. 遍历泛型参数:
for param in &generics.params {
    match param {
        GenericParam::Type(type_param) => {
            let ident = &type_param.ident;
            println!("Type parameter: {}", ident);
        }
        GenericParam::Lifetime(lifetime_param) => {
            let lifetime = &lifetime_param.lifetime;
            println!("Lifetime parameter: {}", lifetime);
        }
        GenericParam::Const(const_param) => {
            let ident = &const_param.ident;
            println!("Const parameter: {}", ident);
        }
    }
}

添加约束:
如果你需要为生成的实现添加额外的约束,可以这样操作:

let mut generics = generics.clone();
generics.make_where_clause()
    .predicates
    .push(parse_quote!(T: MyExtraTrait));

四、有关span

在 Rust 库宏里,Span发挥着关键作用,它能够为错误提示、警告信息以及生成的代码提供精准的位置信息。下面为你详细介绍Span的主要用途和常见操作。

基本概念

Span代表着源代码里的一段范围,借助它,宏生成的代码能够和原始输入的位置建立起联系。这一特性极大地提升了错误信息的可读性,对调试工作有很大帮助。

主要用法

  1. 错误定位

当宏展开时遇到错误,能够精准地指出问题在原始代码中的位置。

use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, ItemFn};

#[proc_macro_attribute]
pub fn require_positive(_attr: TokenStream, item: TokenStream) -> TokenStream {
    let input = parse_macro_input!(item as ItemFn);
    
    // 检查函数是否有参数
    if let syn::FnArg::Typed(arg) = &input.sig.inputs.first().unwrap() {
        // 获取参数名的span
        if let syn::Pat::Ident(ident) = &*arg.pat {
            let span = ident.ident.span();
            
            // 在生成的代码中使用span创建错误
            let error = syn::Error::new(span, "参数必须为正数");
            return error.to_compile_error().into();
        }
    }
    
    TokenStream::from(quote! {#input})
}
  1. 生成代码的位置标记

使生成的代码在错误提示中显示为原始宏调用的位置。

#[proc_macro]
pub fn log_error(input: TokenStream) -> TokenStream {
    let span = proc_macro::Span::call_site(); // 获取宏调用的位置
    
    let output = quote::quote_spanned! {span=>
        eprintln!("错误: {}", #input);
    };
    
    output.into()
}
  1. 跨不同 TokenTree 的 Span 扩展

把多个 Token 的 Span 合并成一个更大的范围。

fn expand_span(tokens: &[proc_macro::TokenTree]) -> proc_macro::Span {
    let start = tokens.first().unwrap().span();
    let end = tokens.last().unwrap().span();
    
    // 合并起始和结束位置的Span
    start.join(end).unwrap_or(start)
}

常用方法

方法 功能描述
Span::call_site() :获取宏调用位置的 Span。 span.join(other) 尝试将当前 Span 与另一个 Span 合并,返回一个覆盖两个 Span 的新 Span。
span.unwrap() : 返回一个新的Span,其位置信息被清除,这有助于隐藏生成代码的来源。
span.error(msg) 在当前 Span位置创建一个错误,错误信息会指向原始代码中对应的位置。
span.warning(msg) 在当前 Span位置创建一个警告,警告信息会指向原始代码中对应的位置。

注意事项

性能方面:频繁进行 Span 操作可能会对宏的展开速度产生一定影响。
兼容性问题:Span 的行为在不同的 Rust 版本中可能会有所不同,在编写库宏时要充分考虑这一点。
调试建议:可以使用span.debug()方法输出 Span 的信息,辅助调试工作。
通过合理运用 Span,你能够编写出更加友好、更易于调试的过程宏。

五、ItemFn

在 Rust 的 syn 库中,ItemFn 类型用于表示函数定义(包括普通函数、关联函数和静态方法)。它是解析和操作 Rust 函数的核心结构,包含函数签名、主体和属性等信息。以下是关于 ItemFn 的详细定义和用法:

  1. ItemFn 的定义结构

ItemFn 结构体表示一个完整的函数定义,其核心字段包括:

pub struct ItemFn {
    pub attrs: Vec<Attribute>,        // 函数属性(如 #[test]、#[inline])
    pub vis: Visibility,              // 可见性修饰符(如 pub、pub(crate))
    pub sig: Signature,               // 函数签名
    pub block: Box<Block>,            // 函数体(代码块)
}

pub struct Signature {
    pub constness: Option<Const>,     // const 关键字(如 const fn)
    pub asyncness: Option<Async>,     // async 关键字
    pub unsafety: Option<Unsafe>,     // unsafe 关键字
    pub abi: Option<Abi>,             // ABI 规范(如 extern "C")
    pub fn_token: Token![fn],         // fn 关键字
    pub ident: Ident,                 // 函数名
    pub generics: Generics,           // 泛型参数(如 <T>)
    pub paren_token: token::Paren,    // 参数列表括号
    pub inputs: FnInputs,             // 参数列表
    pub variadic: Option<Variadic>,   // 可变参数(如 ...)
    pub output: ReturnType,           // 返回类型(如 -> i32)
}

Block 结构体表示一个完整的代码块,其核心字段包括:

pub struct Block {
    pub brace_token: token::Brace,  // 花括号 `{}`
    pub stmts: Vec<Stmt>,           // 代码块中的语句
}

其中,Stmt 是一个枚举类型,表示不同类型的语句:

pub enum Stmt {
    Item(Box<Item>),       // 项(如函数、结构体定义等)
    Local(Box<Local>),     // 本地变量声明(如 let x = 1;Expr(Expr),            // 表达式语句(如 func(); 或 x + y)
    Semicolon(Expr, Token![;]),  // 带分号的表达式(如 x += 1;}
  1. 解析函数定义示例

以下示例展示如何解析一个完整的函数定义:

use syn::{parse_str, ItemFn, ReturnType};
use syn::punctuated::Punctuated;
use syn::token::Comma;

fn main() -> syn::Result<()> {
    // 解析函数定义
    let func: ItemFn = parse_str(r#"
        #[test]
        pub async fn add<T: Add<Output=T>>(a: T, b: T) -> T {
            a + b
        }
    "#)?;

    // 1. 访问函数属性
    for attr in &func.attrs {
        if attr.path.is_ident("test") {
            println!("这是一个测试函数");
        }
    }

    // 2. 访问可见性
    assert!(matches!(func.vis, syn::Visibility::Public(_)));

    // 3. 访问函数签名
    let sig = &func.sig;
    assert!(sig.asyncness.is_some());           // 是 async 函数
    assert_eq!(sig.ident.to_string(), "add");   // 函数名

    // 4. 访问泛型参数
    for param in &sig.generics.params {
        if let syn::GenericParam::Type(ty_param) = param {
            println!("泛型参数: {}", ty_param.ident); // 输出: T
        }
    }

    // 5. 访问函数参数
    for input in &sig.inputs {
        match input {
            syn::FnArg::Typed(pat_type) => {
                let pat = &*pat_type.pat;
                let ty = &*pat_type.ty;
                println!("参数: {:?} 类型: {:?}", pat, ty);
            }
            syn::FnArg::Receiver(_) => {
                println!("方法接收者: self");
            }
        }
    }

    // 6. 访问返回类型
    if let ReturnType::Type(_, ty) = &sig.output {
        println!("返回类型: {:?}", ty); // 输出: T
    }

    // 7. 访问函数体
    let block = &func.block;
    println!("函数体包含 {} 条语句", block.stmts.len());

    Ok(())
}
  1. 在 AST 中识别和处理函数

在解析更复杂的 Rust 代码时,通常需要遍历 AST 并识别函数定义:

use syn::{parse_quote, Item};

fn main() {
    let module: syn::File = parse_quote! {
        mod my_module {
            pub fn public_function() {}
            
            fn private_function() {}
            
            #[cfg(test)]
            mod tests {
                use super::*;
                
                #[test]
                fn test_case() {}
            }
        }
    };

    // 遍历模块中的所有项
    for item in module.items {
        if let Item::Fn(func) = item {
            println!("找到函数: {}", func.sig.ident);
            
            // 检查函数是否为 pub
            if matches!(func.vis, syn::Visibility::Public(_)) {
                println!("  这是一个公共函数");
            }
            
            // 检查函数是否有 #[test] 属性
            let is_test = func.attrs.iter()
                .any(|attr| attr.path.is_ident("test"));
            
            if is_test {
                println!("  这是一个测试函数");
            }
        }
    }
}
  1. 修改和生成函数

在过程宏中,常需要动态生成或修改函数。以下示例展示如何使用 quote 宏生成新的函数:

use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, ItemFn};

#[proc_macro_attribute]
pub fn trace(_attr: TokenStream, item: TokenStream) -> TokenStream {
    let mut func = parse_macro_input!(item as ItemFn);
    
    // 在函数体前添加跟踪代码
    let block = &func.block;
    func.block = Box::new(syn::parse_quote!({
        println!("进入函数: {}", stringify!(#func.sig.ident));
        let result = #block;
        println!("离开函数: {}", stringify!(#func.sig.ident));
        result
    }));

    TokenStream::from(quote!(#func))
}
  1. 处理方法(关联函数)

当解析 impl 块中的方法时,需要特殊处理接收者(self、&self 等):

use syn::{parse_quote, ItemImpl, ImplItem};

fn main() {
    let impl_block: ItemImpl = parse_quote! {
        impl MyStruct {
            pub fn new() -> Self {
                Self {}
            }
            
            pub fn method(&self) -> i32 {
                42
            }
        }
    };

    // 遍历 impl 块中的所有项
    for item in impl_block.items {
        if let ImplItem::Fn(method) = item {
            println!("找到方法: {}", method.sig.ident);
            
            // 检查方法是否有接收者(即是否为实例方法)
            if let Some(receiver) = method.sig.receiver() {
                println!("  这是一个实例方法,接收者: {:?}", receiver);
            } else {
                println!("  这是一个关联函数");
            }
        }
    }
}
  1. 实用方法与注意事项

(1) 检查函数是否为异步

fn is_async(func: &ItemFn) -> bool {
    func.sig.asyncness.is_some()
}

(2) 获取函数参数数量

fn param_count(func: &ItemFn) -> usize {
    func.sig.inputs.len()
}

(3) 注意事项

属性处理:函数属性(如 #[test])可能影响函数的行为,需要特别处理。
泛型与约束:函数的泛型参数和约束存储在 sig.generics中,需递归解析。
错误处理:在实际应用中,应使用模式匹配或 if let 处理可能的变体,避免使用 unwrap()。

总结

ItemFn 是 syn 库中处理 Rust 函数定义的核心类型,主要用于:
解析和识别代码中的函数结构(包括签名、属性、可见性等)
在AST 遍历中访问和处理函数
在过程宏中生成或修改函数

掌握 ItemFn 的用法对于构建自定义 derive宏、代码分析工具和代码转换工具至关重要。

六、File

在 Rust 的 syn 库中,File 类型是解析完整 Rust 源文件的根节点。它表示整个文件的抽象语法树(AST),包含模块项、外部 crate 声明、属性等。

以下是关于 File 的详细定义和用法:

  1. File 的定义结构

File 结构体表示一个完整的 Rust 源文件,其核心字段包括:

pub struct File {
    pub attrs: Vec<Attribute>,    // 文件属性(如 #![crate_type = "lib"])
    pub shebang: Option<String>,  // 文件开头的 shebang 行(如 #!/usr/bin/env rustc)
    pub items: Vec<Item>,         // 文件中的项(如模块、函数、结构体等)
}

其中,Item 是一个枚举类型,表示不同类型的项:

pub enum Item {
    Mod(ItemMod),             // 模块(mod my_module { ... })
    Fn(ItemFn),               // 函数(fn add(a: i32, b: i32) -> i32 { ... })
    Struct(ItemStruct),       // 结构体(struct Point { x: i32, y: i32 })
    Enum(ItemEnum),           // 枚举(enum Color { Red, Green, Blue })
    Trait(ItemTrait),         // trait(trait MyTrait { ... })
    Impl(ItemImpl),           // impl 块(impl MyTrait for MyType { ... })
    // 其他项类型...
}
  1. 解析 Rust 源文件示例

以下示例展示如何解析一个完整的 Rust 文件:

use syn::{parse_file, File, Item};

fn main() -> syn::Result<()> {
    // 解析 Rust 源文件内容
    let source = r#"
        #![crate_type = "lib"]
        
        // 模块文档注释
        /// 数学工具模块
        pub mod math {
            /// 加法函数
            pub fn add(a: i32, b: i32) -> i32 {
                a + b
            }
        }
        
        /// 点结构体
        #[derive(Debug)]
        pub struct Point {
            pub x: i32,
            pub y: i32,
        }
    "#;
    
    let file: File = parse_file(source)?;
    
    // 1. 访问文件属性
    for attr in &file.attrs {
        if let syn::Meta::NameValue(meta) = attr.parse_meta()? {
            if meta.path.is_ident("crate_type") {
                if let syn::Lit::Str(lit_str) = meta.lit {
                    println!("crate 类型: {}", lit_str.value());
                }
            }
        }
    }
    
    // 2. 遍历文件中的所有项
    for item in &file.items {
        match item {
            Item::Mod(module) => {
                println!("找到模块: {}", module.ident);
                
                // 遍历模块中的项
                if let Some((_, content)) = &module.content {
                    for inner_item in content {
                        if let Item::Fn(func) = inner_item {
                            println!("  模块内函数: {}", func.sig.ident);
                        }
                    }
                }
            }
            
            Item::Struct(struct_item) => {
                println!("找到结构体: {}", struct_item.ident);
                
                // 检查结构体是否有 #[derive(Debug)] 属性
                let has_debug = struct_item.attrs.iter()
                    .any(|attr| attr.path.is_ident("derive") && 
                        attr.tokens.to_string().contains("Debug"));
                
                if has_debug {
                    println!("  实现了 Debug trait");
                }
            }
            
            _ => {}
        }
    }

    Ok(())
}
  1. 在 AST 中遍历和处理文件内容

以下示例展示如何遍历文件中的所有函数和结构体:

use syn::{parse_file, File, Item};

fn main() -> syn::Result<()> {
    let source = r#"
        pub fn main() {
            let p = Point { x: 1, y: 2 };
            println!("Point: {:?}", p);
        }
        
        #[derive(Debug)]
        struct Point {
            x: i32,
            y: i32,
        }
    "#;
    
    let file: File = parse_file(source)?;
    
    // 统计信息
    let mut function_count = 0;
    let mut struct_count = 0;
    
    // 遍历文件中的所有项
    for item in &file.items {
        match item {
            Item::Fn(func) => {
                function_count += 1;
                println!("函数: {} (参数: {}, 返回: {:?})",
                    func.sig.ident,
                    func.sig.inputs.len(),
                    func.sig.output
                );
            }
            
            Item::Struct(struct_item) => {
                struct_count += 1;
                println!("结构体: {}", struct_item.ident);
                
                // 计算结构体字段数量
                if let syn::Data::Struct(data_struct) = &struct_item.data {
                    let field_count = match &data_struct.fields {
                        syn::Fields::Named(fields) => fields.named.len(),
                        syn::Fields::Unnamed(fields) => fields.unnamed.len(),
                        syn::Fields::Unit => 0,
                    };
                    println!("  字段数量: {}", field_count);
                }
            }
            
            _ => {}
        }
    }
    
    println!("文件包含 {} 个函数和 {} 个结构体", function_count, struct_count);
    Ok(())
}
  1. 修改和生成 Rust 文件

在过程宏或代码生成工具中,常需要动态生成或修改文件内容:

use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_file, File, Item};

fn add_copyright_header(file: &mut File) {
    // 添加版权注释
    let copyright_attr = syn::parse_quote! {
        #[doc = "Copyright (c) 2023, My Company"]
    };
    file.attrs.insert(0, copyright_attr);
    
    // 在文件开头添加模块文档
    let doc_comment = syn::parse_quote! {
        #[doc = "This module provides utility functions and data structures."]
    };
    file.attrs.insert(1, doc_comment);
}

fn add_version_constant(file: &mut File) {
    // 创建版本常量项
    let version_item = syn::parse_quote! {
        pub const VERSION: &str = "1.0.0";
    };
    
    // 将常量添加到文件项列表的开头
    file.items.insert(0, Item::Const(version_item));
}

#[proc_macro_attribute]
pub fn enhance(_attr: TokenStream, item: TokenStream) -> TokenStream {
    let mut file: File = parse_file(&item.to_string()).unwrap();
    
    // 修改文件
    add_copyright_header(&mut file);
    add_version_constant(&mut file);
    
    TokenStream::from(quote!(#file))
}
  1. 实用方法与注意事项

(1) 从文件读取并解析

fn parse_rust_file(path: &std::path::Path) -> syn::Result<File> {
    let content = std::fs::read_to_string(path)?;
    parse_file(&content)
}

(2) 检查文件是否包含特定项

fn has_module_named(file: &File, name: &str) -> bool {
    file.items.iter().any(|item| {
        if let Item::Mod(module) = item {
            module.ident == name
        } else {
            false
        }
    })
}

(3) 注意事项

项的顺序:文件中的项按源代码中的顺序排列,修改时需注意顺序。
属性处理:文件属性(如 #![feature])和项属性(如#[derive])需分别处理。
错误恢复:解析不完整或无效的 Rust 代码时,syn 可能返回错误,需进行错误处理。

总结

File 是 syn 库中处理完整 Rust 源文件的核心类型,主要用于: 解析和识别 Rust 源文件的结构(属性、模块、函数、结构体等)
在 AST 遍历中访问和处理文件内容 在过程宏或代码生成工具中生成或修改文件 掌握 File的用法对于构建代码分析工具、代码转换工具和自定义过程宏至关重要。

七、token

我们知道,TokenStream 用于表示 Rust 代码的标记流(token stream)。它是解析和生成 Rust 代码的基础数据结构,尤其在编写过程宏(procedural macros)时扮演着关键角色。

而TokenStream 本质上是一个由 token(标记) 组成的序列,每个 token 代表 Rust 代码中的一个最小语法单元(如关键字、标识符、运算符、标点符号等)。例如,代码 fn add(a: i32, b: i32) -> i32 { a + b } 会被解析为包含 fn、add、(、a 等一系列 token 的流。

在 syn 库中,token(标记)的具体定义就显得非常重要。

syn 库通过 token 模块提供了这些基本语法单元的类型定义,用于精确解析和表示 Rust 代码的语法结构。

  1. Token 的定义与作用

Token 在 syn 中表示为带有特殊标记的类型,通常以 Token![…] 的形式存在。例如:

Token![fn]:表示 fn 关键字。
Token![+]:表示 + 运算符。
Token![;]:表示分号 ;。

这些类型用于在 AST(抽象语法树)中精确标记语法结构的边界和组成部分。例如,函数定义中的 fn 关键字、参数列表的括号 ()、代码块的花括号 {} 等,都由对应的 token 类型表示。

  1. 常见 Token 类型

以下是 syn 库中一些常见的 token 类型及其用途:
关键字 Token

Token![fn]       // fn 关键字(函数定义)
Token![struct]   // struct 关键字(结构体定义)
Token![enum]     // enum 关键字(枚举定义)
Token![trait]    // trait 关键字(trait 定义)
Token![impl]     // impl 关键字(实现块)
Token![let]      // let 关键字(变量绑定)
Token![if]       // if 关键字(条件语句)
Token![match]    // match 关键字(模式匹配)
Token![loop]     // loop 关键字(无限循环)

运算符 Token

Token![+]        // 加号
Token![-]        // 减号
Token![*]        // 乘号
Token![/]        // 除号
Token![=]        // 等号
Token![==]       // 等于比较
Token![=>]       // 箭头(如 match 分支或闭包)
Token![->]       // 函数返回类型箭头

标点符号 Token

Token![,]        // 逗号
Token![;]        // 分号
Token![:]        // 冒号
Token![.]        // 点号
Token![..]       // 范围运算符(如 1..10)
Token![..=]      // 包含范围运算符(如 1..=10)
Token![|]        // 竖线(如 match 模式分隔)
Token![&]        // 引用符号
Token![*]        // 解引用或指针符号

括号 Token

token::Paren     // 圆括号 ()
token::Brace     // 花括号 {}
token::Bracket   // 方括号 []
  1. Token 在 AST 结构中的应用

Token 类型通常作为结构体字段存在,用于标记语法结构的位置和边界。例如:
函数定义中的 Token

pub struct ItemFn {
    pub attrs: Vec<Attribute>,        // 属性(如 #[test])
    pub vis: Visibility,              // 可见性(如 pub)
    pub sig: Signature,               // 函数签名
    pub block: Box<Block>,            // 函数体
}

pub struct Signature {
    pub constness: Option<Const>,     // const 关键字
    pub asyncness: Option<Async>,     // async 关键字
    pub unsafety: Option<Unsafe>,     // unsafe 关键字
    pub abi: Option<Abi>,             // ABI 规范
    pub fn_token: Token![fn],         // fn 关键字
    // 其他字段...
}

结构体定义中的 Token

pub struct DataStruct {
    pub struct_token: Token![struct],  // struct 关键字
    pub fields: Fields,                // 结构体字段
    pub semi_token: Option<Token![;]>, // 分号(单元结构体需要)
}
  1. 解析和生成 Token

在 syn 中,Token 类型通常由解析器自动处理,但在生成代码时需要显式使用。例如,使用 quote 宏生成代码时:

use quote::quote;
use syn::{parse_quote, Token};

fn main() {
    // 生成一个简单的函数定义
    let func = parse_quote! {
        fn add(a: i32, b: i32) -> i32 {
            a + b
        }
    };

    // 使用 quote 宏时,Token 会自动插入
    let generated = quote! {
        #func
        
        // 手动插入 Token![+]
        fn multiply(a: i32, b: i32) -> i32 {
            a #Token![*] b
        }
    };

    println!("{}", generated);
}
  1. 注意事项

Token 与 Ident 的区别:

Token![ident] 表示关键字(如 fn、struct),是固定语法。
Ident 表示标识符(如变量名、类型名),是用户定义的名称。

Token 与 Literal 的区别:

Token![42] 不是合法的Token,因为数字属于字面量(LitInt)。
字面量由 syn::Lit 枚举表示,与 Token 是不同的概念。

自定义 Token:

syn 提供了预定义的 Token 类型,但不支持自定义新的 Token 类型。
如果需要表示特定领域的语法,通常使用 Ident 或Lit 结合上下文解析。

总结

Token 在 syn 库中扮演着标记 Rust 代码最小语法单元的角色,是构建 AST 的基础组件。理解 Token 的定义和用法对于解析、操作和生成 Rust 代码至关重要,特别是在开发 procedural macro 或代码分析工具时。

八、quote库

在 Rust 的过程宏(proc-macro)开发中,quote 是一个核心库,用于将 Rust 代码的抽象语法树(AST)结构转换回可读的 Rust 代码片段。它与 syn 库紧密配合,前者负责解析代码为 AST,后者负责将 AST 重新生成为代码。

quote 库的核心功能

quote 库的核心是 quote! 宏,它允许你用类似 Rust 代码的语法来构建代码片段,同时插入动态生成的部分。

基本用法

  1. 引用和插入标识符
use quote::quote;
use syn::{parse_quote, Ident};

let name = Ident::new("MyStruct", proc_macro2::Span::call_site());

// 使用 `quote!` 生成代码
let code = quote! {
    struct #name {
        field: i32,
    }
};

// 输出: struct MyStruct { field: i32, }
println!("{}", code);
#variable:插入变量(如标识符、表达式等)。
  1. 批量生成代码

使用 #(…)* 语法可以批量生成代码:

let fields = vec![
    Ident::new("x", proc_macro2::Span::call_site()),
    Ident::new("y", proc_macro2::Span::call_site()),
];

let code = quote! {
    struct Point {
        #(#fields: i32),*
    }
};
// 输出: struct Point { x: i32, y: i32 }
println!("{}", code);

与 syn 库配合使用

在自定义派生宏中,quote 通常与 syn 结合使用:

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

#[proc_macro_derive(Debug)]
pub fn derive_debug(input: TokenStream) -> TokenStream {
    let DeriveInput { ident, .. } = parse_macro_input!(input as DeriveInput);
    
    let expanded = quote! {
        impl std::fmt::Debug for #ident {
            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
                f.debug_struct(stringify!(#ident)).finish()
            }
        }
    };
    
    TokenStream::from(expanded)
}

高级特性

  1. 条件生成

通过 Rust 逻辑控制生成不同的代码:

let should_implement = true;

let code = if should_implement {
    quote! {
        impl MyTrait for MyStruct {
            fn my_method() {}
        }
    }
} else {
    quote! {} // 空实现
};
  1. 嵌套引用

在 quote! 中嵌套使用 quote!:

let inner = quote! { 42 };
let outer = quote! {
    fn get_value() -> i32 {
        #inner
    }
};

// 输出: fn get_value() -> i32 { 42 }
println!("{}", outer);
  1. 卫生性(Hygiene)

quote 自动处理变量捕获,避免命名冲突:

let var = quote! { x };
let code = quote! {
    let #var = 10;
    let y = #var + 5; // 正确引用上面的 `x`
};

常用技巧

  1. 插入类型和表达式
let ty = parse_quote!(Vec<i32>);
let expr = parse_quote!(vec![1, 2, 3]);

let code = quote! {
    let #ty: #expr;
};
  1. 生成泛型代码
let generics = parse_quote!(<T: Clone>);

let code = quote! {
    fn clone_it#generics(item: T) -> T {
        item.clone()
    }
};
  1. 使用辅助宏

quote_spanned! 可保留代码的位置信息,便于调试:

use quote::quote_spanned;

let spanned_code = quote_spanned! {ident.span() =>
    fn #ident() {}
};

与 proc-macro2 集成

quote 生成的代码是 proc_macro2::TokenStream 类型,可无缝转换为 proc_macro::TokenStream:

// 转换为 proc-macro 所需的类型
let token_stream: proc_macro::TokenStream = code.into();

总结

quote 库是 Rust 过程宏开发的必备工具,它提供了一种直观、安全的方式来生成复杂的 Rust 代码。主要优势包括:
类型安全:通过AST 操作避免生成无效代码。
卫生性:自动处理变量作用域,防止命名冲突。
灵活性:支持批量生成、条件生成和嵌套引用。 配合 syn库,你可以构建强大的代码生成工具,如自定义派生宏、领域特定语言(DSL)等。

在上面的基础上,后面就是多看多写了。


网站公告

今日签到

点亮在社区的每一天
去签到