Как программно получить количество полей структуры?

У меня есть пользовательская структура, как показано ниже:

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

Есть ли API как field_count() или это возможно получить только с помощью макросов?

Если это достижимо с помощью макросов, как это должно быть реализовано?

2 ответа

Решение

Есть ли какие-либо возможные API, как field_count() или это возможно получить только с помощью макросов?

Нет такого встроенного API, который позволял бы вам получать эту информацию во время выполнения. Rust не имеет отражения во время выполнения (см. Этот вопрос для получения дополнительной информации). Но это действительно возможно с помощью макросов proc!

Примечание: proc-макросы отличаются от "macro by example" (который объявлен через macro_rules!). Последний не такой мощный, как proc-макросы.

Если это достижимо с помощью макросов, как это должно быть реализовано?

(Это не введение в proc-макросы; если тема для вас совершенно новая, сначала прочтите введение в другом месте.)

В proc-макросе (например, пользовательском производном) вам как-то нужно получить определение структуры как TokenStream, Де-факто решение использовать TokenStream с синтаксисом Rust это разобрать его через syn:

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

    // ...
}

Тип input является ItemStruct, Как видите, у него есть поле fields типа 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

Это возможно, когда сама структура генерируется макросами - в этом случае вы можете просто считать токены, переданные в макросы, как показано здесь. Вот что я придумал:

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

Детская площадка (с некоторыми тестами)

Недостатком этого подхода (один - их может быть и больше) является то, что добавление атрибута к этой функции нетривиально - например, #[derive(...)] что-то на этом. Другой подход заключается в написании пользовательских макрокоманд получения, но я пока не могу об этом говорить.

Другие вопросы по тегам