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