我有一个自定义结构,如下所示:
struct MyStruct {
first_field: i32,
second_field: String,
third_field: u16,
}
是否可以通过编程方式获取结构字段的数量(例如,通过方法调用field_count()
):
let my_struct = MyStruct::new(10, "second_field", 4);
let field_count = my_struct.field_count(); // Expecting to get 3
对于此结构:
struct MyStruct2 {
first_field: i32,
}
...以下调用应返回1
:
let my_struct_2 = MyStruct2::new(7);
let field_count = my_struct2.field_count(); // Expecting to get count 1
是否有像field_count()
这样的API,还是只能通过宏来获取?
如果宏可以实现这一点,应该如何实现?
答案 0 :(得分:19)
是否有像
field_count()
这样的API,还是只能通过宏来获取?
没有这样的内置API可以让您在运行时获取此信息。 Rust没有运行时反射(有关更多信息,请参见this question)。但是确实可以通过proc-macros!
注意:proc-macros与“通过示例宏”(通过macro_rules!
声明)不同。后者不如proc-macros强大。
如果宏可以实现这一点,应该如何实现?
(这不是proc-macros的简介;如果您对本主题完全陌生,请先阅读其他地方的简介。)
在proc宏(例如,自定义派生)中,您将需要以某种方式获得结构定义为TokenStream
。使用TokenStream
和Rust语法的实际解决方案是通过syn
对其进行解析:
#[proc_macro_derive(FieldCount)]
pub fn derive_field_count(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as ItemStruct);
// ...
}
input
的类型为ItemStruct
。如您所见,它具有类型Fields
的字段fields
。在该字段上,您可以调用iter()
以在结构的所有字段上获得迭代器,然后在其上可以调用count()
:
let field_count = input.fields.iter().count();
现在您有了想要的东西。
也许您想将此field_count()
方法添加到您的类型中。您可以通过自定义派生(通过在此处使用quote
板条箱)来做到这一点:
let name = &input.ident;
let output = quote! {
impl #name {
pub fn field_count() -> usize {
#field_count
}
}
};
// Return output tokenstream
TokenStream::from(output)
然后,在您的应用程序中,您可以编写:
#[derive(FieldCount)]
struct MyStruct {
first_field: i32,
second_field: String,
third_field: u16,
}
MyStruct::field_count(); // returns 3
答案 1 :(得分:7)
由宏生成结构本身是有可能的-在这种情况下,您可以只计算传递到宏中的令牌,如here所示。这就是我想出的:
macro_rules! gen {
($name:ident {$($field:ident : $t:ty),+}) => {
struct $name { $($field: $t),+ }
impl $name {
fn field_count(&self) -> usize {
gen!(@count $($field),+)
}
}
};
(@count $t1:tt, $($t:tt),+) => { 1 + gen!(@count $($t),+) };
(@count $t:tt) => { 1 };
}
Playground(带有一些测试用例)
此方法的缺点(一个-可能会有更多)是在此函数中添加属性并非易事-例如,在其上#[derive(...)]
上添加一些属性。另一种方法是编写自定义的派生宏,但这是我目前无法谈论的事情。