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

Web*_*rix 20 struct rust rust-macros rust-proc-macros

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

struct MyStruct {
    first_field: i32,
    second_field: String,
    third_field: u16,
}
Run Code Online (Sandbox Code Playgroud)

是否有可能以编程方式获取结构字段的数量(例如,通过方法调用field_count()):

let my_struct = MyStruct::new(10, "second_field", 4);
let field_count = my_struct.field_count(); // Expecting to get 3
Run Code Online (Sandbox Code Playgroud)

对于这个结构:

struct MyStruct2 {
    first_field: i32,
}
Run Code Online (Sandbox Code Playgroud)

......以下电话应该返回1:

let my_struct_2 = MyStruct2::new(7);
let field_count = my_struct2.field_count(); // Expecting to get count 1
Run Code Online (Sandbox Code Playgroud)

有没有像这样的API field_count()或者只能通过宏获得它?

如果使用宏可以实现这一点,那么应该如何实现?

Luk*_*odt 19

是否有任何可能的API,field_count()或者只能通过宏获取?

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

注意:proc-macros与"macro by example"(通过声明macro_rules!)不同.后者不如proc-macros强大.

如果使用宏可以实现这一点,那么应该如何实现?

(这不是对proc-macros的介绍;如果这个主题对你来说是全新的,请先阅读其他地方的介绍.)

在proc-macro(例如自定义派生)中,您需要以某种方式获取结构定义TokenStream.使用TokenStream带有Rust语法的事实上的解决方案是通过syn以下方式解析它:

#[proc_macro_derive(FieldCount)]
pub fn derive_field_count(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as ItemStruct);

    // ...
}
Run Code Online (Sandbox Code Playgroud)

类型inputItemStruct.如您所见,它具有fields该类型的字段Fields.在该字段上,您可以调用iter()以获取结构的所有字段上的迭代器,您可以在其上调用count():

let field_count = input.fields.iter().count();
Run Code Online (Sandbox Code Playgroud)

现在你有你想要的东西.

也许您想将此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)
Run Code Online (Sandbox Code Playgroud)

然后,在您的应用程序中,您可以写:

#[derive(FieldCount)]
struct MyStruct {
    first_field: i32,
    second_field: String,
    third_field: u16,
}

MyStruct::field_count(); // returns 3
Run Code Online (Sandbox Code Playgroud)


Cer*_*rus 7

在这种情况下,你可以仅计算传入宏令牌,如图所示-当由宏产生的结构本身有可能在这里.这就是我想出来的:

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 };
}
Run Code Online (Sandbox Code Playgroud)

游乐场(有一些测试用例)

这种方法的缺点(一种 - 可能更多)是为这个函数添加一个属性并不简单 - 例如,#[derive(...)]对它的某些东西.另一种方法是编写自定义派生宏,但这是我现在不能谈论的内容.