2017-02-27 6 views
7

Serde obsługuje stosowania atrybutów niestandardowych, które są używane z #[derive(Serialize)]:Jak przetwarzać enum/struct/field attributes w proceduralnym makrze?

#[derive(Serialize)] 
struct Resource { 
    // Always serialized. 
    name: String, 

    // Never serialized. 
    #[serde(skip_serializing)] 
    hash: String, 

    // Use a method to decide whether the field should be skipped. 
    #[serde(skip_serializing_if = "Map::is_empty")] 
    metadata: Map<String, String>, 
} 

rozumiem jak zaimplementować makra proceduralną (Serialize w tym przykładzie), ale to, co należy zrobić, aby wdrożyć #[serde(skip_serializing)]? Nie udało mi się znaleźć tych informacji w dowolnym miejscu. The docs nawet o tym nie wspominają. Próbowałem spojrzeć na kod źródłowy serde-derive, ale jest to dla mnie bardzo skomplikowane.

Odpowiedz

3

Zaimplementujesz atrybuty na polach jako część makra wyprowadzającego dla struct (możesz zaimplementować tylko wyprowadzające makra dla struktur i wyliczeń).

Serde robi to, sprawdzając każde pole dla atrybutu w strukturach dostarczonych przez syn i odpowiednio zmieniając generowanie kodu.

Można znaleźć odpowiedni kod tutaj: https://github.com/serde-rs/serde/blob/master/serde_codegen_internals/src/attr.rs#L149-L283

+1

Czy możesz podać minimalny przykład tego, jak to zrobić? Widzę kod, o którym wspomniałeś, ale jest też wiele rzeczy zaimplementowanych. –

+2

Jeśli używasz 'syn', możesz uzyskać dostęp do atrybutów pól, uzyskując dostęp do pola' attr' struktury 'Field'. Otrzymujesz strukturę 'Field' przez sprawdzenie pola' body' '' '' body' dla wariantu '' Struct' (https://dtolnay.github.io/syn/syn/enum.Body.html), a następnie wywołanie metody ['fields'] (https://dtolnay.github.io/syn/syn/enum.VariantData.html#method.fields) w celu uzyskania listy' Field's –

9
  1. Najpierw trzeba rejestrować wszystkich atrybutów w tym samym miejscu Aby zarejestrować makro proceduralną. Powiedzmy, że chcemy dodać dwa atrybuty (my nadal nie mów, co będą należeć do: konstrukcjom lub pól lub obu z nich):

    #[proc_macro_derive(FiniteStateMachine, attributes(state_transitions, state_change))] 
    pub fn fxsm(input: TokenStream) -> TokenStream { 
        // ... 
    } 
    

    Potem można już skompilować kod użytkownika z następujących powodów:

    #[derive(Copy, Clone, Debug, FiniteStateMachine)] 
    #[state_change(GameEvent, change_condition)] // optional 
    enum GameState { 
        #[state_transitions(NeedServer, Ready)] 
        Prepare { players: u8 }, 
        #[state_transitions(Prepare, Ready)] 
        NeedServer, 
        #[state_transitions(Prepare)] 
        Ready, 
    } 
    

    Bez tego kompilator daje błąd z komunikatem jak:

    state_change nie należą do żadnego znanego atrybutu.

    Te atrybuty są opcjonalne i wszystko, co zrobiliśmy, pozwala je określić. Kiedy czerpiesz swoje makro proceduralne, możesz sprawdzić wszystko, co chcesz (włączając w to istnienie atrybutów) i panic! pod pewnymi warunkami, mając znaczący komunikat, który zostanie przekazany przez kompilator.

  2. Teraz porozmawiamy o obsłudze tego atrybutu! Zapomnijmy o atrybucie state_transitions, ponieważ jego obsługa nie będzie się zbytnio różnić od obsługi atrybutów struct/enum (w rzeczywistości jest to tylko trochę więcej kodu) i mówimy o state_change. Skrzynia syn zawiera wszystkie potrzebne informacje o definicjach (ale niestety nie implementacje (mam tu na myśli impl), ale to oczywiście wystarcza do obsługi atrybutów). Aby być bardziej szczegółowym, potrzebujemy syn::DeriveInput, syn::Body, syn::Variant, i na koniec syn::MetaItem.

    Aby obsłużyć atrybut pola, musisz przejść przez wszystkie te struktury od jednego do drugiego. Po dotarciu do Vec<syn:: Attribute> - jest to, co chcesz, lista wszystkich atrybutów pola. Tutaj można znaleźć nasz state_transitions. Kiedy go znajdziesz, możesz uzyskać jego zawartość i można to zrobić za pomocą wyliczenia enum o dopasowaniu syn::MetaItem.Wystarczy przeczytać docs :) Oto prosty przykład kodu, który wpada w panikę, kiedy znajdziemy state_change atrybut jakiejś dziedzinie oraz sprawdza czy nasza jednostka docelowa czerpać Copy lub Clone lub żadna z nich:

    #[proc_macro_derive(FiniteStateMachine, attributes(state_transitions, state_change))] 
    pub fn fxsm(input: TokenStream) -> TokenStream { 
        // Construct a string representation of the type definition 
        let s = input.to_string(); 
    
        // Parse the string representation 
        let ast = syn::parse_derive_input(&s).unwrap(); 
    
        // Build the impl 
        let gen = impl_fsm(&ast); 
    
        // Return the generated impl 
        gen.parse().unwrap() 
    } 
    
    fn impl_fsm(ast: &syn::DeriveInput) -> Tokens { 
        const STATE_CHANGE_ATTR_NAME: &'static str = "state_change"; 
    
        if let syn::Body::Enum(ref variants) = ast.body { 
    
         // Looks for state_change attriute (our attribute) 
         if let Some(ref a) = ast.attrs.iter().find(|a| a.name() == STATE_CHANGE_ATTR_NAME) { 
          if let syn::MetaItem::List(_, ref nested) = a.value { 
           panic!("Found our attribute with contents: {:?}", nested); 
          } 
         } 
    
         // Looks for derive impls (not our attribute) 
         if let Some(ref a) = ast.attrs.iter().find(|a| a.name() == "derive") { 
          if let syn::MetaItem::List(_, ref nested) = a.value { 
           if derives(nested, "Copy") { 
            return gen_for_copyable(&ast.ident, &variants, &ast.generics); 
           } else if derives(nested, "Clone") { 
            return gen_for_clonable(&ast.ident, &variants, &ast.generics); 
           } else { 
            panic!("Unable to produce Finite State Machine code on a enum which does not drive Copy nor Clone traits."); 
           } 
          } else { 
           panic!("Unable to produce Finite State Machine code on a enum which does not drive Copy nor Clone traits."); 
          } 
         } else { 
          panic!("How have you been able to call me without derive!?!?"); 
         } 
        } else { 
         panic!("Finite State Machine must be derived on a enum."); 
        } 
    } 
    
    fn derives(nested: &[syn::NestedMetaItem], trait_name: &str) -> bool { 
        nested.iter().find(|n| { 
         if let syn::NestedMetaItem::MetaItem(ref mt) = **n { 
          if let syn::MetaItem::Word(ref id) = *mt { 
           return id == trait_name; 
          } 
          return false 
         } 
         false 
        }).is_some() 
    } 
    

Użytkownik może być zainteresowany czytanieserde_codegen_internals, serde_derive, fxsm-derive. Ostatni odnośnik to w rzeczywistości mój własny projekt, aby samemu wyjaśnić, w jaki sposób używać makr proceduralnych w Rust.


Po pewnym Rust 1,15 i aktualizowanie skrzynię syn, to nie jest już możliwe, by sprawdzić wywodzi o enums/structs jednak wszystko działa w porządku.

Powiązane problemy