Struct 如何以编程方式获取结构的字段数?
我有一个自定义结构,如下所示:Struct 如何以编程方式获取结构的字段数?,struct,rust,rust-macros,rust-proc-macros,Struct,Rust,Rust Macros,Rust Proc Macros,我有一个自定义结构,如下所示: struct MyStruct { first_field: i32, second_field: String, third_field: u16, } 是否可以通过编程方式(例如,通过方法调用field\u count())获取结构字段的数量: 对于此结构: struct MyStruct2 { first_field: i32, } 。。。以下调用应返回1: let my_struct_2 = MyStruct2::new
struct MyStruct {
first_field: i32,
second_field: String,
third_field: u16,
}
是否可以通过编程方式(例如,通过方法调用field\u count()
)获取结构字段的数量:
对于此结构:
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\u count()
的API,或者只有通过宏才能获得这些API
如果这可以通过宏实现,那么应该如何实现呢?当结构本身由宏生成时,这是可能的-在这种情况下,您可以只计算传递到宏中的标记,如图所示。这就是我想到的:
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 };
}
(带有一些测试用例)
这种方法的缺点(一个-可能还有更多)是,向该函数添加属性并不容易,例如,在其上添加#[派生(…)]
某些内容。另一种方法是编写自定义派生宏,但这是我现在不能谈论的事情
是否有任何可能的API,如field\u count()
,或者只有通过宏才能获得这些API
没有这样的内置API允许您在运行时获取此信息。Rust没有运行时反射(有关更多信息,请参阅)。但这确实可以通过proc宏实现
注意:proc宏不同于“示例宏”(通过macro\u规则!
)声明)。后者不如proc宏强大
如果宏可以实现这一点,应该如何实现
(这不是对proc宏的介绍;如果您对该主题完全陌生,请先在其他地方阅读介绍。)
在proc宏(例如自定义派生)中,您可能需要以某种方式将结构定义获取为TokenStream
。使用带有Rust语法的令牌流
的实际解决方案是通过以下方式对其进行解析:
输入的类型为。如您所见,它具有该类型的字段fields
。在该字段上,您可以调用iter()
,在结构的所有字段上获得迭代器,然后在该字段上调用count()
:
现在你有你想要的了
也许您想将这个字段\ u count()
方法添加到您的类型中。您可以通过自定义派生(使用此处的quote
板条箱)执行此操作:
然后,在应用程序中,您可以编写:
#[derive(FieldCount)]
struct MyStruct {
first_field: i32,
second_field: String,
third_field: u16,
}
MyStruct::field_count(); // returns 3
这样做的目的是什么?该语言是静态类型的,因此函数是常量,也就是说,您总是会得到相同的答案,并且没有基于此做出有用的决定。@Jan Hudec,假设您已经静态地编写了程序中某些不同块的计数,并且在某个时候更改了结构并添加了一个新字段。然后,我不想编辑可以自动处理的其他任何地方的计数,这仍然没有说明这些信息的用途。任何依赖于字段数量的代码都将在编译时依赖于它,并且可能也依赖于字段的类型和名称。当字段更改时,它要么无法编译,要么生成,在这种情况下,生成器需要该信息,而自定义派生是正确的工具。您也可以将其设置为自定义属性,而不是派生,有关详细信息,请参见备注:无需运行时反射,运行时内省甚至编译时内省就足够了。事实上,使用派生/属性/proc宏是编译时自省,我在crates.io上找不到类似的功能。因此,我将这个答案打包到以下板条箱中:。希望它对其他人有用,因为不可能向导出宏以外的内容的库中添加新的proc_宏定义。谢谢你@lukas kalbertodt。
#[proc_macro_derive(FieldCount)]
pub fn derive_field_count(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as ItemStruct);
// ...
}
let field_count = input.fields.iter().count();
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