如何以编程方式获取结构的字段数?

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。

以上是关于如何以编程方式获取结构的字段数?的主要内容,如果未能解决你的问题,请参考以下文章

Spark:以编程方式获取集群核心数

以编程方式获取 Resque 队列中的作业数

如何以编程方式填写和获取 Excel 电子表格的 PDF 输出?

获取 Button 和 Textfield 数据,当它们在 swift 5 中以编程方式添加时

如何以编程方式快速使用情节提要 ID 获取当前视图控制器(当前正在呈现)?

以编程方式选择输入字段中的部分文本