Как программно получить количество полей структуры? - PullRequest
0 голосов
/ 14 января 2019

У меня есть пользовательская структура, подобная следующей:

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 ]

0 голосов
/ 14 января 2019

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

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

Примечание: proc-макросы отличаются от "макроса в примере" (который объявлен через 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
0 голосов
/ 14 января 2019

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

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(...)] что-то в ней. Другой подход заключается в написании пользовательских макрокоманд получения, но я пока не могу об этом говорить.

...