宏是rust的灵魂小料和魔法。从println!(“hello world!”)那刻开始,rust世界无宏不成席,无宏不欢。

和显性的声明宏不同,隐藏在代码深处的过程宏,静悄悄。尽管它的出现并不是用来装B的,但如果你喜欢装,过程宏一定不可以不玩。你可以不用,但你不能不懂。对rust人来讲,不理解宏,定会少了一份亲切和挚爱。

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

如果从定义逐步展开,过程宏的线索就会慢慢就浮出水面。本次借助了Bito AI编程来学习过程宏,感觉更不一样。

一、推荐阅读和AI工具准备

1、介绍rust过程宏的参考文章

Rust 中的过程宏 proc-macro,来自知乎

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

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

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

Rust 过程宏

2、syn和quote库的参考文章

与声明宏不同,rust过程宏通常需要用到外部syn和quote库来助力(如果你喜欢手搓的话,也可以不用)。简单可以参考:

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

3、syn、quote和proc-macro2库的源代码

(1)库源代码
这几个库的源代码这步估计省不了。很多细节得一步步摸索。出了问题,还得去看源代码。

syn库文档和源代码
quote库文档和源代码
proc-macro2库文档和源代码

当然你也可以直接上github,下载源代码到本地。
不过我感觉还是docs.rs的文档整理得很清晰,跳转起来也很方便。

(2)库版本号及toml设置

具体请见关于toml部分的设置:

[dependencies]
syn = { version = "2.0.104", features = ["derive","full","extra-traits"] } 
quote ="1.0"
proc-macro2 = "1.0"

特别提示:

上面syn的features中"extra-traits",如果不加的话,你打印就打不了,因为加了后才实现了Debug trait。

4、Bito AI编程

本文得到VScode中Bito插件的技术支持,而且所有代码均编译通过,只有极少数个别问题通过很小的修订。

Bito 是一款基于 GPT-4o, Claude Sonnet 3.5的智能编程辅助工具,提供代码生成、解释、性能优化等功能,支持多种编程语言且无需注册即可使用。

和国内的某包相比,幻觉少了太多,强烈推荐,编译负担非常轻,绝大部分无脑使用。

本文除源代码外,其它代码均用Bito生成。

二、 syn中DeriveInput

说明:下面syn库的代码是2.0版本

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

1、DeriveInput定义

#[cfg_attr(docsrs, doc(cfg(feature = "derive")))]
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、泛型参数(Generics):类型的泛型参数、约束和 where子句。
5、数据类型(Data):类型的具体形式 。

2、DeriveInput解析

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

找到对应的 DeriveInput 信息的代码如下:

use syn::{parse_quote, DeriveInput, Data, Fields, Visibility};
fn main() {
    // Example TokenStream containing a struct with a derive macro
    let code: proc_macro2::TokenStream = parse_quote! {
        #[derive(MyMacro)]
        pub struct Point<T> {
            pub x: f32,
            y: T,
        }
    };
    // Parse the TokenStream into a DeriveInput
    let input: DeriveInput = syn::parse2(code).unwrap();
    // Print the struct name
    println!("Struct Name: {}", input.ident);

    // Print the visibility
    match &input.vis {
        Visibility::Public(_) => println!("Visibility: public"),
        Visibility::Restricted(_) => println!("Visibility: restricted"),
        Visibility::Inherited => println!("Visibility: inherited"),
    }
    // Print the attributes
    for attr in &input.attrs {
        println!("Attribute: {:?}", attr);
    }
    // Match on the data to see if it's a struct, enum, or union
    match &input.data {
        Data::Struct(data_struct) => {
            println!("It's a struct with fields:");
            for field in &data_struct.fields {
                println!("Field: {:?}", field);
                // Print field visibility
                match &field.vis {
                    Visibility::Public(_) => println!("Field Visibility: public"),
                    Visibility::Restricted(_) => println!("Field Visibility: restricted"),
                    Visibility::Inherited => println!("Field Visibility: inherited"),
                }
                // Print field attributes
                for attr in &field.attrs {
                    println!("Field Attribute: {:?}", attr);
                }
            }
        },
        Data::Enum(data_enum) => {
            println!("It's an enum with variants:");
            for variant in &data_enum.variants {
                println!("Variant: {:?}", variant);
                for attr in &variant.attrs {
                    println!("Variant Attribute: {:?}", attr);
                }
            }
        },
        Data::Union(_) => {
            println!("It's a union.");
        },
    }
}

输出:

Struct Name: Point
Visibility: public
Attribute: Attribute { pound_token: Pound, style: AttrStyle::Outer, bracket_token: Bracket, meta: Meta::List { path: Path { leading_colon: None, segments: [PathSegment { ident: Ident(derive), arguments: PathArguments::None }] }, delimiter: MacroDelimiter::Paren(Paren), tokens: TokenStream [Ident { sym: MyMacro }] } }
It's a struct with fields:
Field: Field { attrs: [], vis: Visibility::Public(Pub), mutability: FieldMutability::None, ident: Some(Ident(x)), colon_token: Some(Colon), ty: Type::Path { qself: None, path: Path { leading_colon: None, segments: [PathS)), colon_token: Some(Colon), ty: Type::Path { qself: None, path: Path { leading_colon: None, segments: [PathSegment { ident: Ident(f32), arguments: PathArguments::None }] } } }
Field Visibility: public
Field: Field { attrs: [], vis: Visibility::Inherited, mutability: FieldMutability::None, ident: Some(Ident(y)), colon_token: Some(Colon), ty: Type::Path { qself: None, path: Path { leading_colon: None, segments: [PathSegment { ident: Ident(T), arguments: PathArguments::None }] } } }
Field Visibility: inherited

三、DeriveInput各个字段

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

(一)Attributes

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

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

1、Attribute 定义

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

#[cfg_attr(docsrs, doc(cfg(any(feature = "full", feature = "derive"))))]
pub struct Attribute {
    pub pound_token: Token![#],
    pub style: AttrStyle,
    pub bracket_token: token::Bracket,
    pub meta: Meta,
}

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

#[cfg_attr(docsrs, doc(cfg(any(feature = "full", feature = "derive"))))]
pub enum Meta {
    Path(Path),
    /// A structured list within an attribute, like `derive(Copy, Clone)`.
    List(MetaList),
    /// A name-value pair within an attribute, like `feature = "nightly"`.
    NameValue(MetaNameValue),
}

其中MetaList:

pub struct MetaList {
    pub path: Path,
    pub delimiter: MacroDelimiter,
    pub tokens: TokenStream,
}

进一步Path:

#[cfg_attr(docsrs, doc(cfg(any(feature = "full", feature = "derive"))))]
pub struct Path {
    pub leading_colon: Option<Token![::]>,
    pub segments: Punctuated<PathSegment, Token![::]>,
}

和MetaNameValue:

#[cfg_attr(docsrs, doc(cfg(any(feature = "full", feature = "derive"))))]
pub struct MetaNameValue {
    pub path: Path,
    pub eq_token: Token![=],
    pub value: Expr,
}

再进一步Expr:

#[non_exhaustive]
pub enum Expr {
    Array(ExprArray),
    Assign(ExprAssign),
    Async(ExprAsync),
    Await(ExprAwait),
    Binary(ExprBinary),
    Block(ExprBlock),
    Break(ExprBreak),
    Call(ExprCall),
    Cast(ExprCast),
    Closure(ExprClosure),
    Const(ExprConst),
    Continue(ExprContinue),
    Field(ExprField),
    ForLoop(ExprForLoop),
    Group(ExprGroup),
    If(ExprIf),
    Index(ExprIndex),
    Infer(ExprInfer),
    Let(ExprLet),
    Lit(ExprLit),
    Loop(ExprLoop),
    Macro(ExprMacro),
    Match(ExprMatch),
    MethodCall(ExprMethodCall),
    Paren(ExprParen),
    Path(ExprPath),
    Range(ExprRange),
    RawAddr(ExprRawAddr),
    Reference(ExprReference),
    Repeat(ExprRepeat),
    Return(ExprReturn),
    Struct(ExprStruct),
    Try(ExprTry),
    TryBlock(ExprTryBlock),
    Tuple(ExprTuple),
    Unary(ExprUnary),
    Unsafe(ExprUnsafe),
    Verbatim(TokenStream),
    While(ExprWhile),
    Yield(ExprYield),
}

回头想一下,Attribute从哪儿找?DeriveInput中有,ItemStruct中也有:

#[cfg_attr(docsrs, doc(cfg(feature = "full")))]
pub struct ItemStruct {
    pub attrs: Vec<Attribute>,
    pub vis: Visibility,
    pub struct_token: Token![struct],
    pub ident: Ident,
    pub generics: Generics,
    pub fields: Fields,
    pub semi_token: Option<Token![;]>,
}

也就是找到ItemStruct,那也可以一个个找到Attribute。

2、解析Attribute信息

(1)ItemStruct ->Attribute:

use syn::{parse_str, ItemStruct};
fn main() {
    let code = r#"
        #[derive(Debug)]
        struct MyStruct {
            field1: i32,
            field2: String,
        }
    "#;
    // Parse the struct definition
    let item: ItemStruct = parse_str(code).unwrap();
    // Accessing various components of ItemStruct
    println!("Struct Name: {}", item.ident);
    for attr in item.attrs {
        println!("Attribute: {:?}", attr); //"extra-traits" features加上
    }
    for field in item.fields {
        println!("Field: {:?}", field); //"extra-traits"features加上
    }
}

注意:toml文件中,syn要加上“extra-traits”,否则无法println!.

syn = { version = "2.0.104", features = ["derive","full","extra-traits"] } 
quote ="1.0"

因为你可以在syn文档(https://docs.rs/syn/latest/syn/struct.Attribute.html)中,清晰看到:

Available on crate feature extra-traits only.

输出如下:

Struct Name: MyStruct
Attribute: Attribute { pound_token: Pound, style: AttrStyle::Outer, bracket_token: Bracket, meta: Meta::LStruct Name: MyStruct
Attribute: Attribute { pound_token: Pound, style: AttrStyle::Outer, bracket_token: Bracket, meta: Meta::List { path: Path { leading_colon: None, segments: [PathSegment { ident: Ident(derive), arguments: PathArgStruct Name: MyStruct
Attribute: Attribute { pound_token: Pound, style: AttrStyle::Outer, bracket_token: Bracket, meta: Meta::LAttribute: Attribute { pound_token: Pound, style: AttrStyle::Outer, bracket_token: Bracket, meta: Meta::List { path: Path { leading_colon: None, segments: [PathSegment { ident: Ident(derive), arguments: PathArguments::None }] }, delimiter: MacroDelimiter::Paren(Paren), tokens: TokenStream [Ident { sym: Debug }] } 
}
Field: Field { attrs: [], vis: Visibility::Inherited, mutability: FieldMutability::None, ident: Some(Ident(field1)), colon_token: Some(Colon), ty: Type::Path { qself: None, path: Path { leading_colon: None, segt(field1)), colon_token: Some(Colon), ty: Type::Path { qself: None, path: Path { leading_colon: None, segments: [PathSegment { ident: Ident(i32), arguments: PathArguments::None }] } } }
Field: Field { attrs: [], vis: Visibility::Inherited, mutability: FieldMutability::None, ident: Some(Ident(field2)), colon_token: Some(Colon), ty: Type::Path { qself: None, path: Path { leading_colon: None, segments: [PathSegment { ident: Ident(String), arguments: PathArguments::None }] } } }

(2) tokenstream->Attribute

use syn::{parse_quote, Attribute, ItemStruct};
use proc_macro2::TokenStream;
fn main() {
    // Example of a TokenStream containing a struct with attributes
    let code: TokenStream = parse_quote! {
        #[derive(Debug)]
        struct MyStruct {
            #[serde(rename = "my_field")]
            field: String,
        }
    };
    // Parse the TokenStream into an ItemStruct
    let item: ItemStruct = syn::parse2(code).unwrap();
    // Accessing attributes of the struct
    println!("Struct Name: {}", item.ident);
    for attr in &item.attrs {
        println!("Struct Attribute: {:?}", attr);
    }
    // Accessing attributes of the fields
    for field in item.fields.iter() {
        for attr in &field.attrs {
            println!("Field Attribute: {:?}", attr);
        }
    }
}

输出:

Struct Name: MyStruct
Struct Attribute: Attribute { pound_token: Pound, style: AttrStyle::Outer, bracket_token: Bracket, meta: Meta::List { path: Path { leading_colon: None, segments: [PathSegment { ident: Ident(derive), arguments: PathArguments::None }] }, delimiter: MacroDelimiter::Paren(Paren), tokens: TokenStream [Ident { sym: Debug }] } }
Field Attribute: Attribute { pound_token: Pound, style: AttrStyle::Outer, bracket_token: Bracket, meta: Meta::List { path: Path { leading_colon: None, segments: [PathSegment { 
ident: Ident(serde), arguments: PathArguments::None }] }, delimiter: MacroDelimiter::Paren(Paren), tokens: TokenStream [Ident { sym: rename }, Punct { char: '=', spacing: Alone }, Literal { lit: "my_field" }] } }

(3)DeriveInput ->Attribute

use syn::{parse_quote, DeriveInput};
use proc_macro2::TokenStream;
fn main() {
    // 示例的 TokenStream,包含一个带有派生属性的结构体
    let code: TokenStream = parse_quote! {
        #[derive(Debug, Clone)]
        struct MyStruct {
            field: String,
        }
    };
    // 将 TokenStream 解析为 DeriveInput
    let input: DeriveInput = syn::parse2(code).unwrap();
    // 访问 DeriveInput 的属性
    println!("Struct Name: {}", input.ident);
    
    // 访问派生属性
    for attr in &input.attrs {
        println!("Attribute: {:?}", attr);
    }
    // 访问是否是结构体或枚举
    match input.data {
        syn::Data::Struct(ref data_struct) => {
            println!("It's a struct with fields:");
            for field in &data_struct.fields {
                println!("Field: {:?}", field);
                for attr in &field.attrs {
                    println!("Field Attribute: {:?}", attr);
                }
            }
        },
        syn::Data::Enum(ref data_enum) => {
            println!("It's an enum with variants:");
            for variant in &data_enum.variants {
                println!("Variant: {:?}", variant);
                for attr in &variant.attrs {
                    println!("Variant Attribute: {:?}", attr);
                }
            }
        },
        syn::Data::Union(_) => {
            println!("It's a union.");
        },
    }
}

输出:

Struct Name: MyStruct
Attribute: Attribute { pound_token: Pound, style: AttrStyle::Outer, bracket_token: Bracket, meta: Meta::List { path: Path { leading_colon: None, segments: [PathSegment { ident: Ident(derive), arguments: PathArguments::None }] }, delimiter: MacroDelimiter::Paren(Paren), tokens: TokenStream [Ident { sym: Debug }, Punct { char: ',', spacing: Alone }, Ident { sym: Clone }] } }
It's a struct with fields:
Field: Field { attrs: [], vis: Visibility::Inherited, mutability: FieldMutability::None, ident: Some(Ident(field)), colon_token: Some(Colon), ty: Type::Path { qself: None, path: Path { leading_colon: None, segments: [PathSegment { ident: Ident(String), arguments: 
PathArguments::None }] } } }

(二)Visibility

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

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

1、Visibility 定义

Visibility 是一个枚举类型,表示 Rust 中不同级别的可见性:

#[cfg_attr(docsrs, doc(cfg(any(feature = "full", feature = "derive"))))]
pub enum Visibility {
    /// A public visibility level: `pub`.
    Public(Token![pub]),
    /// A visibility level restricted to some path: `pub(self)` or
    /// `pub(super)` or `pub(crate)` or `pub(in some::module)`.
    Restricted(VisRestricted),
    /// An inherited visibility, which usually means private.
    Inherited,
}

(1)Pub

pub struct Pub {
    pub span: Span,
}

pub struct Span { /* private fields */ }

(2)Restricted, 结构体表示带路径的可见性。

#[cfg_attr(docsrs, doc(cfg(any(feature = "full", feature = "derive"))))]
pub struct VisRestricted {
    pub pub_token: Token![pub],
    pub paren_token: token::Paren,
    pub in_token: Option<Token![in]>,
    pub path: Box<Path>,
}

2、解析Visibility信息

use syn::{parse_quote, ItemStruct, Visibility};
fn main() {
    // 示例的 TokenStream,包含一个带有可见性和属性的结构体
    let code: proc_macro2::TokenStream = parse_quote! {
        #[derive(Debug)]
        pub struct MyStruct {
            field: String,
        }
    };
    // 将 TokenStream 解析为 ItemStruct
    let item: ItemStruct = syn::parse2(code).unwrap();
    // 访问结构体的可见性
    let visibility: &Visibility = &item.vis;
    println!("Struct Visibility: {:?}", visibility);
    // 访问结构体的属性
    for attr in &item.attrs {
        println!("Struct Attribute: {:?}", attr);
    }
    // 访问字段的可见性和属性
    for field in item.fields.iter() {
        let field_visibility: &Visibility = &field.vis;
        println!("Field Visibility: {:?}", field_visibility);
        
        for attr in &field.attrs {
            println!("Field Attribute: {:?}", attr);
        }
    }
}

输出如下:

Struct Visibility: Visibility::Public(Pub)
Struct Attribute: Attribute { pound_token: Pound, style: AttrStyle::Outer, bracket_token: Bracket, meta: Meta::List { path: Path { leading_colon: None, segments: [PathSegment { ident: Ident(derive), arguments: PathArguments::None }] }, delimiter: MacroDelimiter::Paren(Paren), tokens: TokenStream [Ident { sym: Debug }] } }
Field Visibility: Visibility::Inherited

(三)Ident

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

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

1、Ident 定义

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

pub struct Ident { /* private fields */ } // doc.rs
// 具体的源码
#[derive(Clone)]
pub struct Ident {
    inner: imp::Ident,
    _marker: ProcMacroAutoTraits,
}

2、解析 Ident信息

use syn::{parse_str, ItemStruct, Ident};
fn main() {
    // 示例代码字符串
    let code = r#"
        pub struct MyStruct {
            x: f32,
            y: f32,
        }
    "#;
    // 解析代码字符串为 ItemStruct
    let item: ItemStruct = parse_str(code).unwrap();
    // 获取结构体的 Ident
    let struct_ident: Ident = item.ident.clone();
    println!("Struct Ident: {}", struct_ident);
    // 访问字段的 Ident
    for field in item.fields.iter() {
        if let Some(ident) = &field.ident {
            println!("Field Ident: {}", ident);
        }
    }
}

输出:

Struct Ident: MyStruct
Field Ident: x
Field Ident: y

也可通过tokenstream得到:

use syn::{parse2, ItemStruct, Ident};
use proc_macro2::TokenStream;
fn main() {
    // 示例的 TokenStream,包含一个结构体定义
    let code: TokenStream = quote::quote! {
        pub struct MyStruct {
            x: f32,
            y: f32,
        }
    };
    // 将 TokenStream 解析为 ItemStruct
    let item: ItemStruct = parse2(code).unwrap();
    // 获取结构体的 Ident
    let struct_ident: Ident = item.ident.clone();
    println!("Struct Ident: {}", struct_ident);
    // 访问字段的 Ident
    for field in item.fields.iter() {
        if let Some(ident) = &field.ident {
            println!("Field Ident: {}", ident);
        }
    }
}

同样可以。

3、Ident的生成

可以使用 Ident::new 方法直接创建一个新的 Ident。这个方法需要两个参数:标识符的名称和一个 Span。Span 用于表示代码的来源位置,通常可以使用 Span::call_site() 来表示当前调用的位置。

use proc_macro2::{Ident, Span};
use quote::quote;

fn main() {
    // 创建新的Ident
    let call_ident = Ident::new("call_ident", Span::call_site());
    //组合成新Ident
    let ident = Ident::new("demo", Span::call_site());
    let expanded = quote! { let #ident = 10; };
    let temp_ident = Ident::new(&format!("new_{}", ident), Span::call_site());
    let expanded = quote! { let #temp_ident = 10; };

}

(四)Data的结构

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

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

1、Data定义

#[cfg_attr(docsrs, doc(cfg(feature = "derive")))]
pub enum Data {
    Struct(DataStruct),
    Enum(DataEnum),
    Union(DataUnion),
}

(1)关于DataStruct

// 更进一步:
#[cfg_attr(docsrs, doc(cfg(feature = "derive")))]
    pub struct DataStruct {
        pub struct_token: Token![struct],
        pub fields: Fields,
        pub semi_token: Option<Token![;]>,
    }
// --->MORE
pub struct Struct {
    pub span: Span,
}
// --->MORE
pub enum Fields {
    Named(FieldsNamed),
    Unnamed(FieldsUnnamed),
    Unit,
}
#[cfg_attr(docsrs, doc(cfg(any(feature = "full", feature = "derive"))))]
pub struct FieldsNamed {
    pub brace_token: token::Brace,
    pub named: Punctuated<Field, Token![,]>,
}
#[cfg_attr(docsrs, doc(cfg(any(feature = "full", feature = "derive"))))]
pub struct FieldsUnnamed {
    pub paren_token: token::Paren,
    pub unnamed: Punctuated<Field, Token![,]>,
}

pub struct Punctuated<T, P> {
    inner: Vec<(T, P)>,
    last: Option<Box<T>>,
}
//说明:Punctuated<T, P>实现了一系列类似Vec的方法;
//impl<T, P> Punctuated<T, P>
//pub fn iter(&self) -> Iter<'_, T> 
//pub fn get(&self, index: usize) -> Option<&T>
//故可以进行fields.named.iter().map(|f| {})等操作
//fields.named ->FieldsNamed.named

(2)关于DataEnum

#[cfg_attr(docsrs, doc(cfg(feature = "derive")))]

 pub struct DataEnum {
      pub enum_token: Token![enum],
      pub brace_token: token::Brace,
      pub variants: Punctuated<Variant, Token![,]>,
    }

(3)关于DataUnion

#[cfg_attr(docsrs, doc(cfg(feature = "derive")))]
pub struct DataUnion {
    pub union_token: Token![union],
    pub fields: FieldsNamed,
}

更进一步:

pub struct Union {
    pub span: Span,
}

2、关于Data类信息解析代码

use syn::{parse_str, DeriveInput, Data};
fn main() {
    // 示例代码字符串
    let code = r#"
        #[derive(Debug)]
        pub struct MyStruct {
            x: f32,
            y: String,
        }
    "#;
    // 解析代码字符串为 DeriveInput
    let input: DeriveInput = parse_str(code).unwrap();
    // 获取 Data 类型信息
    match input.data {
        Data::Struct(ref data_struct) => {
            println!("It's a struct with fields:");
            for field in &data_struct.fields {
                println!("Field: {:?}", field);
            }
        },
        Data::Enum(ref data_enum) => {
            println!("It's an enum with variants:");
            for variant in &data_enum.variants {
                println!("Variant: {:?}", variant);
            }
        },
        Data::Union(_) => {
            println!("It's a union.");
        },
    }
}

输出:

It's a struct with fields:
Field: Field { attrs: [], vis: Visibility::Inherited, mutability: FieldMutability::None, ident: Some(Ident(x)), colon_token: Some(Colon), ty: Type::Path { qself: None, path: Path { leading_colon: None, segments: [PathSegment { ident: Ident(f32), arguments: PathArguments::None }] } } }
Field: Field { attrs: [], vis: Visibility::Inherited, mutability: FieldMutability::None, ident: Some(Ident(y)), colon_token: Some(Colon), ty: Type::Path { qself: None, path: Path { leading_colon: None, segments: [PathSegment { ident: Ident(String), arguments: PathArguments::None }] } } }

(五)Generics

1、Generics定义

#[cfg_attr(docsrs, doc(cfg(any(feature = "full", feature = "derive"))))]
pub struct Generics {
    pub lt_token: Option<Token![<]>,
    pub params: Punctuated<GenericParam, Token![,]>,
    pub gt_token: Option<Token![>]>,
    pub where_clause: Option<WhereClause>,
}

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

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

2、解析Generics信息

use syn::{parse_quote, ItemStruct, Generics};
use proc_macro2::TokenStream;
fn main() {
    // 示例的 TokenStream,包含一个带有泛型的结构体定义
    let code: TokenStream = parse_quote! {
        #[derive(MyMacro)]
        pub struct Point<T, U: Clone + 'static>
        where
            T: std::fmt::Debug + Copy,
        {
            x: T,
            y: U,
        }
    };
    // 将 TokenStream 解析为 ItemStruct
    let item: ItemStruct = syn::parse2(code).unwrap();
    // 访问 Generics
    let generics: &Generics = &item.generics;
    // 打印 Generics 信息
    println!("Generics: {:?}", generics);
    // 打印每个泛型参数
    for param in generics.params.iter() {
        println!("Generic Parameter: {:?}", param);
    }
}

输出:

Generics: Generics { lt_token: Some(Lt), params: [GenericParam::Type(TypeParam { attrs: [], ident: Ident(T), colon_token: None, bounds: [], eq_token: None, default: None }), Comma, GenericParam::Type(TypeParam { attrs: [], ident: Ident(U), colon_token: Some(Colon), bounds: [TypeParamBound::Trait(TraitBound { paren_token: None, modifier: TraitBoundModifier::None, lifetimes: None, path: Path { leading_colon: None, segments: [PathSegment { ident: Ident(Clone), arguments: PathArguments::None }] } }), Plus, TypeParamBound::Lifetime { apostrophe: Span, ident: Ident(static) }], eq_token: None, default: None })], gt_token: Some(Gt), where_clause: Some(WhereClause { 
where_token: Where, predicates: [WherePredicate::Type(PredicateType { lifetimes: None, bounded_ty: Type::Path { qself: None, path: Path { leading_colon: None, segments: [PathSegment { ident: Ident(T), arguments: PathArguments::None }] } }, colon_token: Colon, bounds: [TypeParamBound::Trait(TraitBound { paren_token: None, modifier: 
TraitBoundModifier::None, lifetimes: None, path: Path { leading_colon: None, segments: [PathSegment { ident: Ident(std), arguments: PathArguments::None }, PathSep, PathSegment { ident: Ident(fmt), arguments: PathArguments::None }, PathSep, PathSegment { ident: Ident(Debug), arguments: PathArguments::None }] } }), Plus, TypeParamBound::Trait(TraitBound { paren_token: None, modifier: TraitBoundModifier::None, lifetimes: None, path: Path { leading_colon: None, segments: [PathSegment { ident: Ident(Copy), arguments: PathArguments::None }] } })] }), Comma] }) }
Generic Parameter: GenericParam::Type(TypeParam { attrs: [], ident: Ident(T), colon_token: None, bounds: [], eq_token: None, default: None })
Generic Parameter: GenericParam::Type(TypeParam { attrs: [], ident: Ident(U), colon_token: Some(Colon), bounds: [TypeParamBound::Trait(TraitBound { paren_token: None, modifier: TraitBoundModifier::None, lifetimes: None, path: Path { leading_colon: None, segments: [PathSegment { ident: Ident(Clone), arguments: PathArguments::None }] } }), Plus, TypeParamBound::Lifetime { apostrophe: Span, ident: Ident(static) }], eq_token: None, default: None })

四、ItemFn

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

1、ItemFn 定义

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

#[cfg_attr(docsrs, doc(cfg(feature = "full")))]
pub struct ItemFn {
    pub attrs: Vec<Attribute>,
    pub vis: Visibility,
    pub sig: Signature,
    pub block: Box<Block>,
}

(1)Signature

#[cfg_attr(docsrs, doc(cfg(feature = "full")))]
pub struct Signature {
    pub constness: Option<Token![const]>,
    pub asyncness: Option<Token![async]>,
    pub unsafety: Option<Token![unsafe]>,
    pub abi: Option<Abi>,
    pub fn_token: Token![fn],
    pub ident: Ident,
    pub generics: Generics,
    pub paren_token: token::Paren,
    pub inputs: Punctuated<FnArg, Token![,]>,
    pub variadic: Option<Variadic>,
    pub output: ReturnType,
}

(2)Block

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

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

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

#[cfg_attr(docsrs, doc(cfg(feature = "full")))]
pub enum Stmt {
    /// A local (let) binding.
    Local(Local),
    /// An item definition.
    Item(Item),
    /// Expression, with or without trailing semicolon.
    Expr(Expr, Option<Token![;]>),
    /// A macro invocation in statement position.
    ///
    /// Syntactically it's ambiguous which other kind of statement this
    /// macro would expand to. It can be any of local variable (`let`),
    /// item, or expression.
    Macro(StmtMacro),
}

2、 解析ItemFn

use syn::{parse_str, ItemFn};
fn main() {
    // 示例代码字符串
    let code = r#"
        #[test]
        pub async fn add<T: std::ops::Add<Output=T>>(a: T, b: T) -> T {
            a + b
        }
    "#;
    // 解析代码字符串为 ItemFn
    let item: ItemFn = parse_str(code).unwrap();
    // 打印函数名称
    println!("Function Name: {}", item.sig.ident);
    // 打印函数的可见性
    println!("Visibility: {:?}", item.vis);
    // 打印函数的异步状态
    println!("Is Async: {}", item.sig.asyncness.is_some());
    // 打印函数的参数
    for input in item.sig.inputs.iter() {
        println!("Input: {:?}", input);
    }
    // 打印函数的返回类型
    println!("Return Type: {:?}", item.sig.output);
}

输出:

Function Name: add
Visibility: Visibility::Public(Pub)
Is Async: true
Input: FnArg::Typed(PatType { attrs: [], pat: Pat::Ident { attrs: [], by_ref: None, mutability: None, ident: Ident(a), subpat: None }, colon_token: Colon, ty: Type::Path { qself: None, path: Path { leading_colon: None, segments: [PathSegment { ident: Ident(T), arguments: PathArguments::None }] } } })
Input: FnArg::Typed(PatType { attrs: [], pat: Pat::Ident { attrs: [], by_ref: None, mutability: None, ident: Ident(b), subpat: None }, colon_token: Colon, ty: Type::Path { qself: None, path: Path { leading_colon: None, segments: [PathSegment { ident: Ident(T), arguments: PathArguments::None }] } } })
Return Type: ReturnType::Type(RArrow, Type::Path { qself: None, path: Path { leading_colon: None, segments: [PathSegment { ident: Ident(T), arguments: PathArguments::None }] } })

五、File

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

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

1、File 的定义结构

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

#[cfg_attr(docsrs, doc(cfg(feature = "full")))]
pub struct File {
    pub shebang: Option<String>,
    pub attrs: Vec<Attribute>,
    pub items: Vec<Item>,
}

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

#[non_exhaustive]
pub enum Item {
    Const(ItemConst),
    Enum(ItemEnum),
    ExternCrate(ItemExternCrate),
    Fn(ItemFn),
    ForeignMod(ItemForeignMod),
    Impl(ItemImpl),
    Macro(ItemMacro),
    Mod(ItemMod),
    Static(ItemStatic),
    Struct(ItemStruct),
    Trait(ItemTrait),
    TraitAlias(ItemTraitAlias),
    Type(ItemType),
    Union(ItemUnion),
    Use(ItemUse),
    Verbatim(TokenStream),
}

2、File信息解析

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

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(())
}

输出:

File Attributes: [Attribute { pound_token: Pound, style: AttrStyle::Inner(Not), bracket_token: Bracket, meta: Meta::NameValue { path: Path { leading_colon: None, segments: [PathSegment { ident: Ident(crate_type), arguments: PathArguments::None }] }, eq_token: Eq, value: Expr::Lit { attrs: [], lit: Lit::Str { token: "lib" } } } }]
Item: Item::Mod { attrs: [Attribute { pound_token: Pound, style: AttrStyle::Outer, bracket_token: Bracket, 
meta: Meta::NameValue { path: Path { leading_colon: None, segments: [PathSegment { ident: Ident(doc), arguments: PathArguments::None }] }, eq_token: Eq, value: Expr::Lit { attrs: [], lit: Lit::Str { token: " 数学工
具模块" } } } }], vis: Visibility::Public(Pub), unsafety: None, mod_token: Mod, ident: Ident(math), content: Some((Brace, [Item::Fn { attrs: [Attribute { pound_token: Pound, style: AttrStyle::Outer, bracket_token: 
Bracket, meta: Meta::NameValue { path: Path { leading_colon: None, segments: [PathSegment { ident: Ident(doc), arguments: PathArguments::None }] }, eq_token: Eq, value: Expr::Lit { attrs: [], lit: Lit::Str { token: " 加法函数" } } } }], vis: Visibility::Public(Pub), sig: Signature { constness: None, asyncness: None, unsafety: None, abi: None, fn_token: Fn, ident: Ident(add), generics: Generics { lt_token: None, params: [], gt_token: None, where_clause: None }, paren_token: Paren, inputs: [FnArg::Typed(PatType { attrs: [], pat: Pat::Ident { attrs: [], by_ref: None, mutability: None, ident: Ident(a), subpat: None }, colon_token: Colon, 
ty: Type::Path { qself: None, path: Path { leading_colon: None, segments: [PathSegment { ident: Ident(i32), arguments: PathArguments::None }] } } }), Comma, FnArg::Typed(PatType { attrs: [], pat: Pat::Ident { attrs: [], by_ref: None, mutability: None, ident: Ident(b), subpat: None }, colon_token: Colon, ty: Type::Path { qself: None, path: Path { leading_colon: None, segments: [PathSegment { ident: Ident(i32), arguments: PathArguments::None }] } } })], variadic: None, output: ReturnType::Type(RArrow, Type::Path { qself: None, path: Path { leading_colon: None, segments: [PathSegment { ident: Ident(i32), arguments: PathArguments::None }] } }) }, block: Block { brace_token: Brace, stmts: [Stmt::Expr(Expr::Binary { attrs: [], left: Expr::Path { attrs: [], qself: None, path: Path { leading_colon: None, segments: [PathSegment { ident: Ident(a), arguments: PathArguments::None }] } }, op: BinOp::Add(Plus), right: Expr::Path { attrs: [], qself: None, path: Path { leading_colon: None, segments: [PathSegment { ident: Ident(b), arguments: PathArguments::None }] } } }, None)] } }])), semi: None }
Item: Item::Struct { attrs: [Attribute { pound_token: Pound, style: AttrStyle::Outer, bracket_token: Bracket, meta: Meta::NameValue { path: Path { leading_colon: None, segments: [PathSegment { ident: Ident(doc), arguments: PathArguments::None }] }, eq_token: Eq, value: Expr::Lit { attrs: [], lit: Lit::Str { token: " 点 
结构体" } } } }, Attribute { pound_token: Pound, style: AttrStyle::Outer, bracket_token: Bracket, meta: Meta::List { path: Path { leading_colon: None, segments: [PathSegment { ident: Ident(derive), arguments: PathArguments::None }] }, delimiter: MacroDelimiter::Paren(Paren), tokens: TokenStream [Ident { sym: Debug }] } 
}], vis: Visibility::Public(Pub), struct_token: Struct, ident: Ident(Point), generics: Generics { lt_token: None, params: [], gt_token: None, where_clause: None }, fields: Fields::Named { brace_token: Brace, named: [Field { attrs: [], vis: Visibility::Public(Pub), mutability: FieldMutability::None, ident: Some(Ident(x)), colon_token: Some(Colon), ty: Type::Path { qself: None, path: Path { leading_colon: None, segments: [PathSegment { ident: Ident(i32), arguments: PathArguments::None }] } } }, Comma, Field { attrs: [], vis: Visibility::Public(Pub), mutability: FieldMutability::None, ident: Some(Ident(y)), colon_token: Some(Colon), ty: 
Type::Path { qself: None, path: Path { leading_colon: None, segments: [PathSegment { ident: Ident(i32), arguments: PathArguments::None }] } } }, Comma] }, semi_token: None }

六、proc_macro2库

在过程宏中,我们还会使用到proc_macro2库。

proc_macro2 提供了对 Rust 过程宏的支持,允许开发者在编写自定义宏时处理 Rust 代码的抽象语法树(AST)。这个库是 proc-macro 库的一个扩展,提供了更丰富的功能和更灵活的 API。

关于AST:

强烈建议看一看网站:

https://astexplorer.net/

当你登陆这个网站,并选择好rust后,这个网站上可以显示rust代码对应的AST。这样,你就可以直观看到代码对应的AST信息。

在这里插入图片描述
关于File,Item,等等,都可以具体清晰的显示,进而对Tokenstream也有相应的了解。

1、核心概念

(1)TokenStream

proc_macro2 提供了 TokenStream 类型,用于表示一系列的 Rust tokens。它可以用于生成和操作 Rust 代码,支持将代码片段作为数据结构进行处理。

proc_macro2::TokenStream定义如下:

#[derive(Clone)]
pub struct TokenStream {
    inner: imp::TokenStream,
    _marker: ProcMacroAutoTraits,
}

更进一步看imp::TokenStream:

#[derive(Clone)]
pub(crate) enum TokenStream {
    Compiler(DeferredTokenStream),
    Fallback(fallback::TokenStream),
}
#[derive(Clone)]
pub(crate) struct DeferredTokenStream {
    stream: proc_macro::TokenStream,
    extra: Vec<proc_macro::TokenTree>,
}

而fallback::TokenStream,有:

#[derive(Clone)]
pub(crate) struct TokenStream {
    inner: RcVec<TokenTree>,
}

可以看出,TokenStream是Tokentree的封装。

(2)Tokentree

proc_macro2 提供了对 Rust 语言中基本构造块(tokens)的支持,包括关键字、标识符、符号、字面量等。在 Rust 中,token 是构成源代码的基本单元,包括关键字、标识符、符号、字面量等。例如,fn、let、+、42 等都是 tokens。

proc_macro2 库通过 TokenStream 类型来表示一系列的 tokens。

在 proc_macro2 中,Token由Tokentree来定义,具体如下:

#[derive(Clone)]
pub enum TokenTree {
    /// A token stream surrounded by bracket delimiters.
    Group(Group),
    /// An identifier.
    Ident(Ident),
    /// A single punctuation character (`+`, `,`, `$`, etc.).
    Punct(Punct),
    /// A literal character (`'a'`), string (`"hello"`), number (`2.3`), etc.
    Literal(Literal),
}

其中:
Ident:表示标识符,例如函数名、变量名等。
Literal:表示字面量,例如数字、字符串等。
Punct:表示标点符号,例如分号、逗号等。
Group:表示一组 token,通常用于表示括号中的内容。

代码上更进一步:

#[derive(Clone)]
pub struct Group {
    inner: imp::Group,
}
#[derive(Clone)]
pub struct Ident {
    inner: imp::Ident,
    _marker: ProcMacroAutoTraits,
}
#[derive(Clone)]
pub struct Punct {
    ch: char,
    spacing: Spacing,
    span: Span,
}
#[derive(Clone)]
pub struct Literal {
    inner: imp::Literal,
    _marker: ProcMacroAutoTraits,
}

2、Tokentree解析

(1)遍历与访问

可以遍历 TokenStream 中的每个 token,并根据需要进行处理。你可以使用 iter() 方法来访问其中的每个 token。

(2)组合与生成

可以通过组合不同的 tokens 来生成新的代码。例如,可以将多个 Ident、Literal 和 Punct 组合成一个完整的函数定义。

(3)转换:

可以将 TokenStream 转换为字符串,便于调试和输出。

代码举例:

use proc_macro2::{TokenStream, TokenTree};
use quote::quote;
fn main() {
    // 使用 quote! 宏生成 TokenStream
    let tokens: TokenStream = quote! {
        fn hello() {
            println!("Hello, world!");
        }
    };
    // 遍历 TokenStream 中的每个 token
    for token in tokens {
        match token {
            TokenTree::Ident(ident) => println!("Identifier: {}", ident),
            TokenTree::Punct(punct) => println!("Punctuation: {}", punct),
            TokenTree::Literal(literal) => println!("Literal: {}", literal),
            TokenTree::Group(group) => println!("Group: {:?}", group),
        }
    }
}

输出:

Identifier: fn
Identifier: hello
Group: Group { delimiter: Parenthesis, stream: TokenStream [] }
Group: Group { delimiter: Brace, stream: TokenStream [Ident { sym: println }, Punct { char: '!', spacing: Alone }, Group { delimiter: Parenthesis, stream: TokenStream [Literal 
{ lit: "Hello, world!" }] }, Punct { char: ';', spacing: Alone }] }

3、TokenStream解析

TokenStream可以通过以下几种常见方式:

1、使用 quote! 宏

quote! 宏是生成 TokenStream 的最常用方法。你可以在宏中编写 Rust 代码,quote! 会将其转换为 TokenStream。

use quote::quote;
use proc_macro2::TokenStream;
fn main() {
    // 使用 quote! 宏创建 TokenStream
    let tokens: TokenStream = quote! {
        pub fn my_function() {
            println!("Hello, world!");
        }
    };
    // 打印生成的 TokenStream
    println!("{}", tokens);
}

输出:

pub fn my_function () { println ! ("Hello, world!") ; }

2、使用 syn::parse_str

如果你有一个 Rust 代码的字符串表示,可以使用 syn::parse_str 函数将其解析为 TokenStream。

use syn::parse_str;
use proc_macro2::TokenStream;
fn main() {
    let code = r#"
        pub fn my_function() {
            println!("Hello, world!");
        }
    "#;
    // 将字符串解析为 TokenStream
    let tokens: TokenStream = parse_str(code).unwrap();
    // 打印生成的 TokenStream
    println!("{}", tokens);
}

输出:

pub fn my_function () { println ! ("Hello, world!") ; }

3、从现有的 TokenStream

如果你已经有一个 TokenStream,可以直接使用它,或者将其与其他 tokens 组合。

use quote::quote;
use proc_macro2::TokenStream;
fn main() {
    let additional_tokens: TokenStream = quote! {
        let x = 10;
    };
    // 与其他 tokens 组合
    let combined_tokens: TokenStream = quote! {
        pub fn my_function() {
            #additional_tokens
            println!("Hello, world!");
        }
    };
    // 打印组合后的 TokenStream
    println!("{}", combined_tokens);
}

输出:

pub fn my_function () { let x = 10 ; println ! ("Hello, world!") ; }

4、使用 TokenStream::from

你还可以通过 TokenStream::from 方法从单个 token 或 token 的序列创建 TokenStream。

use proc_macro2::{TokenStream, TokenTree};
fn main() {
    // 从单个 token 创建 TokenStream
    let single_token: TokenStream = TokenStream::from(TokenTree::Ident(proc_macro2::Ident::new("my_function", proc_macro2::Span::call_site())));
    // 打印创建的 TokenStream
    println!("{}", single_token);
}

输出:

my_function

总结
使用 quote!:这是生成 TokenStream 的最常用方法。
使用 syn::parse_str:可以将字符串表示的 Rust 代码解析为 TokenStream。
操作现有的 Tokens:如果你已经有 TokenStream,可以直接使用或组合它们。
使用 TokenStream::from:从单个 token 创建 TokenStream。

七、quote库

quote 库是 Rust 中用于生成代码的一个强大工具,特别是在编写宏时。它允许你以类似于 Rust 语法的方式编写代码片段,并将其转换为 TokenStream。以下是 quote 库的一些常见用法及其详细说明:

1、基本用法

最基本的用法是使用 quote! 宏来生成代码。你可以在 quote! 宏中编写 Rust 代码,生成的代码会被转换为 TokenStream。

use quote::quote;
fn main() {
    let tokens = quote! {
        fn hello() {
            println!("Hello, world!");
        }
    };
    println!("{}", tokens); // 打印生成的代码
}

输出:

fn hello () { println ! ("Hello, world!") ; }

2、插入变量

你可以在 quote! 中插入变量。使用 # 符号可以将变量的值嵌入到生成的代码中。

use quote::quote;
fn main() {
    let name = "world";
    let tokens = quote! {
        fn hello() {
            println!("Hello, {}!", #name);
        }
    };
    println!("{}", tokens); // 打印生成的代码
}

输出:

fn hello () { println ! ("Hello, {}!" , "world") ; }

3、 生成结构体和枚举

quote 库非常适合生成结构体和枚举的定义。你可以使用变量和循环来动态生成这些结构。

use quote::quote;
fn main() {
    let struct_name = "Point";
    let fields = vec!["x: i32", "y: i32"];
    let tokens = quote! {
        struct #struct_name {
            #( #fields ),*
        }
    };
    println!("{}", tokens); // 打印生成的结构体定义
}

输出:

struct "Point" { "x: i32" , "y: i32" }

4、处理泛型

quote 也可以用于生成带有泛型的类型和函数。

use quote::quote;
fn main() {
    let generics = "T";
    let tokens = quote! {
        fn add<T>(a: T, b: T) -> T {
            a + b
        }
    };
    println!("{}", tokens); // 打印生成的带有泛型的函数
}

输出:

fn add < T > (a : T , b : T) -> T { a + b }

5、生成实现块

你可以使用 quote 生成实现块(impl 块),方便为结构体或枚举添加方法。

use quote::quote;
fn main() {
    let struct_name = "Point";
    let tokens = quote! {
        impl #struct_name {
            fn new(x: i32, y: i32) -> Self {
                Self { x, y }
            }
        }
    };
    println!("{}", tokens); // 打印生成的实现块
}

输出:

impl "Point" { fn new (x : i32 , y : i32) -> Self { Self { x , y } } 
}

6、结合 syn 库

quote 通常与 syn 库结合使用,syn 用于解析 Rust 代码并生成 AST,quote 则用于生成新的代码。例如,你可以解析一个结构体,然后为它生成实现块。

use syn::{parse_quote, ItemStruct};
use quote::quote;
fn main() {
    let input: ItemStruct = parse_quote! {
        struct Point {
            x: i32,
            y: i32,
        }
    };
    let struct_name = &input.ident;
    let tokens = quote! {
        impl #struct_name {
            fn new(x: i32, y: i32) -> Self {
                Self { x, y }
            }
        }
    };
    println!("{}", tokens); // 打印生成的实现块
}

输出:

impl Point { fn new (x : i32 , y : i32) -> Self { Self { x , y } } }

7、 生成宏

quote 也可以用于生成宏。你可以定义一个宏,并使用 quote 来生成宏的内容。

use quote::quote;
macro_rules! generate_function {
    ($name:ident) => {
        quote! {
            fn $name() {
                println!("This is function {}!", stringify!($name));
            }
        }
    };
}
fn main() {
    let tokens = generate_function!(hello);
    println!("{}", tokens); // 打印生成的函数
}

输出:

fn hello () { println ! ("This is function {}!" , stringify ! (hello)) ; }

总结

quote 库允许你以类似 Rust 语法的方式编写代码,并将其转换为 TokenStream。
可以插入变量、生成结构体、枚举、实现块和带有泛型的函数。
通常与 syn 库结合使用,以便解析和生成 Rust 代码。
适用于编写宏和代码生成的场景。

八、Span

我们在syn库,proc-macro2库中可以看到有关Span:比如

// syn库:
pub struct Union {
    pub span: Span,
}
pub struct Pub {
    pub span: Span,
}

还有:

// syn库:Ident
/// Returns the span of this `Ident`.
 pub fn span(&self) -> Span {
     Span::_new(self.inner.span())
 }
 pub fn set_span(&mut self, span: Span) {
   self.inner.set_span(span.inner);
 }

到底Span是指啥?如果从syn库struct Pub定义,找Span,最后指向了proc_macro2库源代码:

/// A region of source code, along with macro expansion information.
#[derive(Copy, Clone)]
pub struct Span {
    inner: imp::Span,
    _marker: ProcMacroAutoTraits,
}

你再看看imp::Span,proc_macro2库源代码提示:

#[derive(Copy, Clone)]
pub(crate) enum Span {
    Compiler(proc_macro::Span),
    Fallback(fallback::Span),
}

Union 和 Pub 结构体都包含一个 span 字段,表示这两个结构体在源代码中的位置信息。这意味着每当你创建一个 Union 或 Pub 的实例时,你可以跟踪它们在原始代码中的位置,方便进行错误报告和调试。

在 Rust 的 syn 库中,Span 是一个非常重要的概念,它用于表示代码的位置信息。Span 提供了关于源代码中某个 token 或语法元素的位置信息,包括它在源代码中的起始位置和结束位置。

以下是关于 Span 的一些详细说明:

1、Span 的定义

Span 是 proc-macro2 库中的一个结构体,通常与 syn 库一起使用。它用于表示 Rust 源代码中的一个范围,可以是单个 token 的位置,也可以是多个 token 的范围。

2、Span 的用途

Span 主要用于以下几个方面:

错误报告:在编译器或宏中,Span 可以用来提供错误信息的位置信息,帮助开发者找到代码中的问题。
**代码生成:**在生成代码时,Span 可以用于保持源代码的位置信息,以便在生成的代码中保留原始代码的上下文。
**调试信息:**在分析和调试代码时,Span 可以帮助开发者理解代码的结构和位置。

3、创建 Span

Span 可以通过以下几种方式创建:

Span::call_site():表示当前调用位置的 Span,通常用于宏定义中。

Span::mixed_site():表示混合的 Span,通常用于表示跨越多个源文件的情况。

4、Span 的使用示例

在 syn 库中,许多类型(如 Ident、Token 等)都包含一个 Span 字段,表示它们在源代码中的位置。例如:

use syn::{Ident, parse_quote};
use proc_macro2::Span;
fn main() {
    // 创建一个 Ident,并指定其 Span
    let my_ident: Ident = Ident::new("my_variable", Span::call_site());
    // 打印 Ident 和其 Span
    println!("Ident: {}, Span: {:?}", my_ident, my_ident.span());
}

输出:

Ident: my_variable, Span: Span

总结
Span 在 Rust 的 syn 库中是一个用于表示代码位置信息的重要结构。它不仅在编译器中用于错误报告和调试,也在宏和代码生成中扮演着关键角色。通过 Span,开发者可以更好地理解和处理 Rust 代码的结构和上下文。

九、几个常用宏

除了quote!宏,常见的宏还有parse_macro_input!和parse_quote!等。

1、syn::parse_macro_input!

macro_rules! parse_macro_input {
    ($tokenstream:ident as $ty:ty) => { ... };
    ($tokenstream:ident with $parser:path) => { ... };
    ($tokenstream:ident) => { ... };
}

常见用法:

let ast:DeriveInput = parse_macro_input!(input);
// 等价下面
let ast = parse_macro_input!(input as DeriveInput);
//同样:
let input = parse_macro_input!(item as ItemFn);
//等价:
let input:ItemFn = parse_macro_input!(item );

2、syn::parse_quote!

#[cfg_attr(docsrs, doc(cfg(all(feature = "parsing", feature = "printing"))))]
#[macro_export]
macro_rules! parse_quote {
    ($($tt:tt)*) => {
        $crate::__private::parse_quote($crate::__private::quote::quote!($($tt)*))
    };
}

实际上调用的就是quote!,返回的是实现了syn::parse trait的类型,具体如下

pub trait Parse: Sized {
    // Required method
    fn parse(input: ParseStream<'_>) -> Result<Self>;
}

举例:

use quote::quote;
use syn::{parse_quote, Stmt,ItemStruct};
fn main() {
    let name = quote!(v);
    let ty = quote!(u8);
    let stmt: Stmt = parse_quote! {
        let #name: #ty = Default::default();
    };
    println!("{:#?}", stmt);
    let input: ItemStruct = parse_quote! {
        struct Point {
            x: i32,
            y: i32,
        }
    };
    println!("{:#?}", input);
}

输出:

Stmt::Local {
    attrs: [],
    let_token: Let,      
    pat: Pat::Type {     
        attrs: [],       
        pat: Pat::Ident {
            attrs: [],
            by_ref: None,
            mutability: None,
            ident: Ident(
                v,
            ),
            subpat: None,
        },
        colon_token: Colon,
        ty: Type::Path {
            qself: None,
            path: Path {
                leading_colon: None,
                segments: [
                    PathSegment {
                        ident: Ident(
                            u8,
                        ),
                        arguments: PathArguments::None,
                    },
                ],
            },
        },
    },
    init: Some(
        LocalInit {
            eq_token: Eq,
            expr: Expr::Call {
                attrs: [],
                func: Expr::Path {
                    attrs: [],
                    qself: None,
                    path: Path {
                        leading_colon: None,
                        segments: [
                            PathSegment {
                                ident: Ident(
                                    Default,
                                ),
                                arguments: PathArguments::None,  
                            },
                            PathSep,
                            PathSegment {
                                ident: Ident(
                                    default,
                                ),
                                arguments: PathArguments::None,  
                            },
                        ],
                    },
                },
                paren_token: Paren,
                args: [],
            },
            diverge: None,
        },
    ),
    semi_token: Semi,
}
ItemStruct {
    attrs: [],
    vis: Visibility::Inherited,
    struct_token: Struct,
    ident: Ident(
        Point,
    ),
    generics: Generics {
        lt_token: None,
        params: [],
        gt_token: None,
        where_clause: None,
    },
    fields: Fields::Named {
        brace_token: Brace,
        named: [
            Field {
                attrs: [],
                vis: Visibility::Inherited,
                mutability: FieldMutability::None,
                ident: Some(
                    Ident(
                        x,
                    ),
                ),
                colon_token: Some(
                    Colon,
                ),
                ty: Type::Path {
                    qself: None,
                    path: Path {
                        leading_colon: None,
                        segments: [
                            PathSegment {
                                ident: Ident(
                                    i32,
                                ),
                                arguments: PathArguments::None,  
                            },
                        ],
                    },
                },
            },
            Comma,
            Field {
                attrs: [],
                vis: Visibility::Inherited,
                mutability: FieldMutability::None,
                ident: Some(
                    Ident(
                        y,
                    ),
                ),
                colon_token: Some(
                    Colon,
                ),
                ty: Type::Path {
                    qself: None,
                    path: Path {
                        leading_colon: None,
                        segments: [
                            PathSegment {
                                ident: Ident(
                                    i32,
                                ),
                                arguments: PathArguments::None,  
                            },
                        ],
                    },
                },
            },
            Comma,
        ],
    },
    semi_token: None,
}

而上面的 Stmt ,ItemStruct都实现了parse trait。打开下面的URL,就可以看到实现了parse trait的全部类型。

https://docs.rs/syn/latest/syn/parse/trait.Parse.html

到这里,过程宏的学习旅途要告一段落了,后面就是多看多写了。
AI编程助力是个好东西,太有用了,希望能成为一种思维和工作方式。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐