27

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

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

是否有类似的 APIfield_count()或者只能通过宏获得?

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

4

2 回答 2

26

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

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

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

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

这不是对 proc-macros 的介绍;如果该主题对您来说是全新的,请先阅读其他地方的介绍。

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

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

    // ...
}

的类型inputItemStruct。如您所见,它具有fieldstype的字段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
于 2019-01-14T08:28:39.243 回答
9

It's possible when the struct itself is generated by the macros - in this case you can just count tokens passed into macros, as shown here. That's what I've come up with:

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 (with some test cases)

The downside for this approach (one - there could be more) is that it's not trivial to add an attribute to this function - for example, to #[derive(...)] something on it. Another approach would be to write the custom derive macros, but this is something that I can't speak about for now.

于 2019-01-14T08:24:48.380 回答