如何以编程方式获取结构的字段数?
Posted
技术标签:
【中文标题】如何以编程方式获取结构的字段数?【英文标题】:How to programmatically get the number of fields of a struct? 【发布时间】:2019-06-08 04:49:02 【问题描述】:我有一个自定义结构,如下所示:
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 或者只能通过宏来获取?
如果这可以通过宏实现,应该如何实现?
【问题讨论】:
这样做的目的是什么?语言是静态类型的,所以函数是不变的,也就是说你总是会得到相同的答案,并且没有基于此做出有用的决定。 @Jan Hudec,假设您已经在程序的某些不同块上静态编写了计数,并且有时您更改了结构并添加了一个新字段。然后,我不想在其他可以自动处理的地方编辑计数 这仍然没有说明这些信息有什么用处。任何依赖于字段数量的代码都将在编译时依赖它,并且可能也依赖于字段的类型和名称。当字段发生变化时,它要么编译失败,要么生成,在这种情况下,生成器需要这些信息——而自定义派生是合适的工具。 【参考方案1】:当结构本身由宏生成时,这是可能的 - 在这种情况下,您可以只计算传递给宏的令牌,如 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(...)]
上的某些东西。另一种方法是编写自定义派生宏,但我暂时无法谈论。
【讨论】:
【参考方案2】:是否有任何可能的 API,例如
field_count()
,或者只能通过宏获得?
没有这样的内置 API 可以让您在运行时获取此信息。 Rust 没有运行时反射(有关更多信息,请参阅this question)。但确实可以通过 proc-macros 实现!
注意:proc-macros 不同于“macro by example”(通过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
crate)来做到这一点:
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
【讨论】:
除了派生之外,您还可以将其设为自定义属性,详情请参阅the reference 注意:运行时反射不是必需的,运行时自省甚至编译时自省就足够了。事实上,事实证明,使用派生/属性/proc-macro 是编译时自省。 我在 crates.io 上找不到类似的功能。所以,我把这个答案打包到了下面的箱子里:github.com/discosultan/field-count。希望它对某人有用,因为不可能将新的 proc_macro 定义添加到导出宏以外的东西的库中。谢谢@lukas-kalbertodt。以上是关于如何以编程方式获取结构的字段数?的主要内容,如果未能解决你的问题,请参考以下文章
如何以编程方式填写和获取 Excel 电子表格的 PDF 输出?
获取 Button 和 Textfield 数据,当它们在 swift 5 中以编程方式添加时