计算并生成重复结构字段的 Rust 宏
Rust macro that counts and generates repetitive struct fields
我想编写一个从整数参数生成不同结构的宏。例如,make_struct!(3)
可能会生成如下内容:
pub struct MyStruct3 {
field_0: u32,
field_1: u32,
field_2: u32
}
将“3”文字转换为可用于生成代码的数字的最佳方法是什么?我应该使用 macro_rules!
还是 proc-macro
?
您需要一个程序属性宏和相当多的管道。 Github 上有一个示例实现;请记住,它的边缘非常粗糙,但开始时效果很好。
目标是:
#[derivefields(u32, "field", 3)]
struct MyStruct {
foo: u32
}
转译为:
struct MyStruct {
pub field_0: u32,
pub field_1: u32,
pub field_2: u32,
foo: u32
}
为此,首先,我们要确定几件事。我们将需要一个 struct
来轻松存储和检索我们的参数:
struct MacroInput {
pub field_type: syn::Type,
pub field_name: String,
pub field_count: u64
}
其余为管道工程:
impl Parse for MacroInput {
fn parse(input: ParseStream) -> syn::Result<Self> {
let field_type = input.parse::<syn::Type>()?;
let _comma = input.parse::<syn::token::Comma>()?;
let field_name = input.parse::<syn::LitStr>()?;
let _comma = input.parse::<syn::token::Comma>()?;
let count = input.parse::<syn::LitInt>()?;
Ok(MacroInput {
field_type: field_type,
field_name: field_name.value(),
field_count: count.base10_parse().unwrap()
})
}
}
这在我们的 struct
上定义了 syn::Parse
并允许我们使用 syn::parse_macro_input!()
轻松解析我们的参数。
#[proc_macro_attribute]
pub fn derivefields(attr: TokenStream, item: TokenStream) -> TokenStream {
let input = syn::parse_macro_input!(attr as MacroInput);
let mut found_struct = false; // We actually need a struct
item.into_iter().map(|r| {
match &r {
&proc_macro::TokenTree::Ident(ref ident) if ident.to_string() == "struct" => { // react on keyword "struct" so we don't randomly modify non-structs
found_struct = true;
r
},
&proc_macro::TokenTree::Group(ref group) if group.delimiter() == proc_macro::Delimiter::Brace && found_struct == true => { // Opening brackets for the struct
let mut stream = proc_macro::TokenStream::new();
stream.extend((0..input.field_count).fold(vec![], |mut state:Vec<proc_macro::TokenStream>, i| {
let field_name_str = format!("{}_{}", input.field_name, i);
let field_name = Ident::new(&field_name_str, Span::call_site());
let field_type = input.field_type.clone();
state.push(quote!(pub #field_name: #field_type,
).into());
state
}).into_iter());
stream.extend(group.stream());
proc_macro::TokenTree::Group(
proc_macro::Group::new(
proc_macro::Delimiter::Brace,
stream
)
)
}
_ => r
}
}).collect()
}
修饰符的行为创建了一个新的TokenStream
并添加了我们的字段first。这是极其重要;假设提供的结构是 struct Foo { bar: u8 }
;附加 last 会由于缺少 ,
而导致解析错误。前置允许我们不必关心这一点,因为结构中的尾随逗号不是解析错误。
一旦我们有了这个 TokenStream
,我们就用 quote::quote!()
生成的标记连续地 extend()
它;这使我们不必自己构建令牌片段。一个陷阱是字段名称需要转换为 Ident
(否则会被引用,这不是我们想要的)。
然后我们 return 将 TokenStream
修改为 TokenTree::Group
以表示这确实是一个由方括号分隔的块。
这样做,我们也解决了一些问题:
- 因为没有命名成员的结构(例如
pub struct Foo(u32)
)实际上从来没有左括号,这个宏是一个空操作
- 它不会操作任何不是结构的项目
- 它也将在没有成员的情况下空操作结构
我想编写一个从整数参数生成不同结构的宏。例如,make_struct!(3)
可能会生成如下内容:
pub struct MyStruct3 {
field_0: u32,
field_1: u32,
field_2: u32
}
将“3”文字转换为可用于生成代码的数字的最佳方法是什么?我应该使用 macro_rules!
还是 proc-macro
?
您需要一个程序属性宏和相当多的管道。 Github 上有一个示例实现;请记住,它的边缘非常粗糙,但开始时效果很好。
目标是:
#[derivefields(u32, "field", 3)]
struct MyStruct {
foo: u32
}
转译为:
struct MyStruct {
pub field_0: u32,
pub field_1: u32,
pub field_2: u32,
foo: u32
}
为此,首先,我们要确定几件事。我们将需要一个 struct
来轻松存储和检索我们的参数:
struct MacroInput {
pub field_type: syn::Type,
pub field_name: String,
pub field_count: u64
}
其余为管道工程:
impl Parse for MacroInput {
fn parse(input: ParseStream) -> syn::Result<Self> {
let field_type = input.parse::<syn::Type>()?;
let _comma = input.parse::<syn::token::Comma>()?;
let field_name = input.parse::<syn::LitStr>()?;
let _comma = input.parse::<syn::token::Comma>()?;
let count = input.parse::<syn::LitInt>()?;
Ok(MacroInput {
field_type: field_type,
field_name: field_name.value(),
field_count: count.base10_parse().unwrap()
})
}
}
这在我们的 struct
上定义了 syn::Parse
并允许我们使用 syn::parse_macro_input!()
轻松解析我们的参数。
#[proc_macro_attribute]
pub fn derivefields(attr: TokenStream, item: TokenStream) -> TokenStream {
let input = syn::parse_macro_input!(attr as MacroInput);
let mut found_struct = false; // We actually need a struct
item.into_iter().map(|r| {
match &r {
&proc_macro::TokenTree::Ident(ref ident) if ident.to_string() == "struct" => { // react on keyword "struct" so we don't randomly modify non-structs
found_struct = true;
r
},
&proc_macro::TokenTree::Group(ref group) if group.delimiter() == proc_macro::Delimiter::Brace && found_struct == true => { // Opening brackets for the struct
let mut stream = proc_macro::TokenStream::new();
stream.extend((0..input.field_count).fold(vec![], |mut state:Vec<proc_macro::TokenStream>, i| {
let field_name_str = format!("{}_{}", input.field_name, i);
let field_name = Ident::new(&field_name_str, Span::call_site());
let field_type = input.field_type.clone();
state.push(quote!(pub #field_name: #field_type,
).into());
state
}).into_iter());
stream.extend(group.stream());
proc_macro::TokenTree::Group(
proc_macro::Group::new(
proc_macro::Delimiter::Brace,
stream
)
)
}
_ => r
}
}).collect()
}
修饰符的行为创建了一个新的TokenStream
并添加了我们的字段first。这是极其重要;假设提供的结构是 struct Foo { bar: u8 }
;附加 last 会由于缺少 ,
而导致解析错误。前置允许我们不必关心这一点,因为结构中的尾随逗号不是解析错误。
一旦我们有了这个 TokenStream
,我们就用 quote::quote!()
生成的标记连续地 extend()
它;这使我们不必自己构建令牌片段。一个陷阱是字段名称需要转换为 Ident
(否则会被引用,这不是我们想要的)。
然后我们 return 将 TokenStream
修改为 TokenTree::Group
以表示这确实是一个由方括号分隔的块。
这样做,我们也解决了一些问题:
- 因为没有命名成员的结构(例如
pub struct Foo(u32)
)实际上从来没有左括号,这个宏是一个空操作 - 它不会操作任何不是结构的项目
- 它也将在没有成员的情况下空操作结构