Rust:AI编程、过程宏与源码
个人看法,宏就是rust的灵魂和魔法。从hello world那刻开始,rust的世界就永远无法离开宏。过程宏并不是用来装B的,但如果你喜欢装,过程宏一定不可以不玩。你可以不用,但你不能不懂。对rust人来讲,不理解宏,定会少了一份亲切和挚爱。虽然声明宏相对比较简单(不是本次的话题),但是rust的过程宏,却看起来有点复杂,容易上头。如果从定义逐步展开,过程宏的线索就会慢慢就浮出水面。本次借助了B
宏是rust的灵魂小料和魔法。从println!(“hello world!”)那刻开始,rust世界无宏不成席,无宏不欢。
和显性的声明宏不同,隐藏在代码深处的过程宏,静悄悄。尽管它的出现并不是用来装B的,但如果你喜欢装,过程宏一定不可以不玩。你可以不用,但你不能不懂。对rust人来讲,不理解宏,定会少了一份亲切和挚爱。
虽然声明宏相对比较简单(不是本次的话题),但是rust的过程宏,却看起来有点复杂,容易上头。
如果从定义逐步展开,过程宏的线索就会慢慢就浮出水面。本次借助了Bito AI编程来学习过程宏,感觉更不一样。
一、推荐阅读和AI工具准备
1、介绍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] 或 #的形式存在,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 结构体主要包含以下字段:
- params:泛型参数列表,可能包含:
类型参数(如 T) 生命周期参数(如 'a) 常量参数(如 N: usize)- where_clause:可选的 where 子句,用于指定额外的约束条件。
- 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编程助力是个好东西,太有用了,希望能成为一种思维和工作方式。
更多推荐
所有评论(0)