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

31

我有一个自定义结构体,其如下所示:

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,还是只能通过宏获取?

如果可以用宏实现,应该如何实现?


2
这样做的目的是什么?该语言是静态类型的,因此函数将是常数,即您将始终获得相同的答案,并且没有有用的决策可以基于此做出。 - Jan Hudec
5
假设你已经在程序的不同块中静态地写入了计数,并且在某个时候你改变了结构并添加了一个新字段。那么,我不希望编辑其他任何地方的计数,这可以自动处理。@Jan Hudec - Akiner Alkan
1
这仍然没有说明这些信息的用途。任何依赖于字段数量的代码都将在编译时依赖它,并且可能还会依赖于字段的类型和名称。当字段发生变化时,它要么无法编译,要么是生成的,此时生成器需要这些信息-自定义派生就是正确的工具。 - Jan Hudec
2个回答

32
是否有类似于field_count()的API可用,或者只能通过宏获取此信息?在运行时,没有这样的内置API可以让您获取此信息。Rust没有运行时反射(有关更多信息,请参见此问题)。但是通过proc-macros是确实可能的!请注意:proc-macros与“宏示例”(通过macro_rules!声明)不同。后者不如proc-macros强大。

如果使用宏可以实现这一点,应该如何实现呢?在proc-macro(例如自定义推导)中,您需要以某种方式将结构定义作为TokenStream进行获取。使用带有Rust语法的TokenStream的事实上解决方案是通过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

1
你可以将它改为自定义属性,而不是使用派生(derive),详情请见参考文档 - hellow
注意:运行时反射并非必需,运行时内省甚至编译时内省都足够了。实际上,使用派生/属性/过程宏是编译时内省。 - Matthieu M.
1
我在crates.io上找不到类似的功能。因此,我将这个答案打包成了以下crate:https://github.com/discosultan/field-count。希望对某些人有用,因为无法向导出其他内容的库添加新的proc_macro定义。谢谢@lukas-kalbertodt。 - Jaanus Varus

9

如果结构体本身是由宏生成的,那么就可以通过计算传递到宏中的标记数量来实现,具体请参见这里。以下是我想出的方法:

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(...)] 。另一种方法是编写自定义派生宏,但目前我无法对此进行讨论。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接