2017-02-27 13 views
7

SERDE支持應用所使用#[derive(Serialize)]自定義屬性:如何在程序宏中處理枚舉/結構/字段屬性?

#[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>, 
} 

我明白瞭如何實現程序宏(Serialize在這個例子中),但我應該怎麼做才能實現#[serde(skip_serializing)]?我無法在任何地方找到這些信息。 docs甚至沒有提到這一點。我試圖看看serde-derive源代碼,但它對我來說非常複雜。

回答

3

您在字段上實現屬性作爲結構的派生宏的一部分(您只能爲結構和枚舉實現派生宏)。

Serde通過檢查每個字段的syn提供的結構中的屬性並相應地更改代碼生成來完成此操作。

您可以找到相關的代碼在這裏:https://github.com/serde-rs/serde/blob/master/serde_codegen_internals/src/attr.rs#L149-L283

+1

您能否提供一個如何做到這一點的簡單例子?我看到你提到的代碼,但也有很多東西也實現了。 –

+2

如果你使用'syn',那麼你可以通過訪問'Field'結構的'attr'字段來訪問字段的屬性。通過檢查['Struct'變體]的'syn :: MacroInput'的'body'字段來獲得'Field'結構體(https://dtolnay.github.io/syn/syn/enum.Body.html ),然後調用['fields'方法](https://dtolnay.github.io/syn/syn/enum.VariantData.html#method.fields)來獲取Field的列表 –

9
  1. 首先,你必須註冊您的所有屬性在您註冊程序的宏同一個地方。比方說,我們要添加兩個屬性(我們仍然不說話會有什麼他們屬於:結構或字段或兩者):

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

    那之後,你可能已經編譯下面的用戶密碼:

    #[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, 
    } 
    

    沒有這個編譯器會給出一個錯誤與像消息:

    state_change不屬於任何已知的屬性。

    這些屬性是可選的,我們所做的只是允許它們被指定。當你派生你的程序宏時,你可以檢查你想要的所有東西(包括屬性的存在)和panic!在某些情況下編譯器會告訴有意義的消息。

  2. 現在我們將討論處理屬性!讓我們忘掉state_transitions屬性,因爲它的處理方式與處理結構/枚舉屬性(實際上它只是一點點代碼)並沒有太大的區別,並且談論了state_changesyn箱子爲您提供了所有關於定義的必要信息(但不幸的是(不是很實現)(我在這裏談論的是impl),但這對於處理屬性當然是足夠的)。更詳細地說,我們需要syn::DeriveInput,syn::Body,syn::Variant,syn::Attribute,最後是syn::MetaItem

    要處理字段的屬性,您需要從一個到另一個遍歷所有這些結構。當你到達Vec<syn:: Attribute> - 這是你想要的,一個字段的所有屬性的列表。在這裏我們可以找到我們的state_transitions。當你找到它時,你可能想要獲得它的內容,這可以通過使用匹配syn::MetaItem枚舉來完成。剛剛看過的文檔:)下面是一個簡單的例子代碼時,我們發現了一些領域state_change屬性,它嚇壞了,再加上它確實檢查我們的目標實體獲得CopyClone或者兩者都不是的:

    #[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() 
    } 
    

您可能有興趣閱讀serde_codegen_internals,serde_derive,fxsm-derive。最後一個鏈接實際上是我自己的項目,向我自己解釋如何在Rust中使用程序宏。


一定的防鏽1.15和更新的SYN箱子之後,就不再可能檢查enums/structs的導出,但一切工作好。