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

How to programmatically get the number of fields of a struct?

我有一个如下所示的自定义结构:

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,
}

...下面的调用应该return 1:

let my_struct_2 = MyStruct2::new(7);
let field_count = my_struct2.field_count(); // Expecting to get count 1

有没有像 field_count() 这样的 API 还是只能通过宏来获得?

如果这可以通过宏实现,应该如何实现?

当结构本身由宏生成时,这是可能的 - 在这种情况下,您可以只计算传递到宏中的标记,如 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(...)] 添加属性。另一种方法是编写自定义派生宏,但我现在不能谈论这个。

Are there any possible API like field_count() or is it only possible to get that via macros?

没有这样的内置 API 可以让您在运行时获取此信息。 Rust 没有运行时反射(有关更多信息,请参阅 )。但确实可以通过 proc-macros!

注意:proc-macros 不同于 "macro by example"(通过 macro_rules! 声明)。后者没有proc-macros强大

If this is achievable with macros, how should it be implemented?

(这不是 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. As you can see, it has the field fields of the type 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