Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I have a custom struct like the following:

struct MyStruct {
    first_field: i32,
    second_field: String,
    third_field: u16,
}

Is it possible to get the number of struct fields programmatically (like, for example, via a method call field_count()):

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

For this struct:

struct MyStruct2 {
    first_field: i32,
}

... the following call should return 1:

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

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

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

like image 804
Akiner Alkan Avatar asked Jan 14 '19 07:01

Akiner Alkan


2 Answers

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

There is no such built-in API that would allow you to get this information at runtime. Rust does not have runtime reflection (see this question for more information). But it is indeed possible via proc-macros!

Note: proc-macros are different from "macro by example" (which is declared via macro_rules!). The latter is not as powerful as proc-macros.

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

(This is not an introduction into proc-macros; if the topic is completely new to you, first read an introduction elsewhere.)

In the proc-macro (for example a custom derive), you would somehow need to get the struct definition as TokenStream. The de-facto solution to use a TokenStream with Rust syntax is to parse it via syn:

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

    // ...
}

The type of input is ItemStruct. As you can see, it has the field fields of the type Fields. On that field you can call iter() to get an iterator over all fields of the struct, on which in turn you could call count():

let field_count = input.fields.iter().count();

Now you have what you want.

Maybe you want to add this field_count() method to your type. You can do that via the custom derive (by using the quote crate here):

let name = &input.ident;

let output = quote! {
    impl #name {
        pub fn field_count() -> usize {
            #field_count
        }
    }
};

// Return output tokenstream
TokenStream::from(output)

Then, in your application, you can write:

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

MyStruct::field_count(); // returns 3
like image 161
Lukas Kalbertodt Avatar answered Nov 18 '22 20:11

Lukas Kalbertodt


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.

like image 23
Cerberus Avatar answered Nov 18 '22 20:11

Cerberus