Rust 如何在过程宏中处理枚举/结构/字段属性?

Rust 如何在过程宏中处理枚举/结构/字段属性?,rust,Rust,Serde支持应用与#[派生(序列化)]一起使用的自定义属性: #[派生(序列化)] 结构资源{ //总是序列化的。 名称:String, //从未序列化。 #[serde(跳过序列化)] hash:String, //使用方法决定是否应跳过该字段。 #[serde(跳过序列化,如果=“Map::is\u empty”)] 元数据:地图, } 我了解如何实现过程宏(在本例中序列化),但如何实现#[serde(skip_serialization)]?我在任何地方都找不到这个信息。他们甚至都不提

Serde支持应用与
#[派生(序列化)]
一起使用的自定义属性:

#[派生(序列化)]
结构资源{
//总是序列化的。
名称:String,
//从未序列化。
#[serde(跳过序列化)]
hash:String,
//使用方法决定是否应跳过该字段。
#[serde(跳过序列化,如果=“Map::is\u empty”)]
元数据:地图,
}

我了解如何实现过程宏(
在本例中序列化
),但如何实现
#[serde(skip_serialization)]
?我在任何地方都找不到这个信息。他们甚至都不提这件事。我曾尝试查看
serde-derivate
源代码,但它对我来说非常复杂。

将字段上的属性作为结构的派生宏的一部分来实现(只能为结构和枚举实现派生宏)

Serde通过检查
syn
提供的结构中的每个字段的属性并相应地更改代码生成来实现这一点

您可以在此处找到相关代码:

  • 首先,必须在注册过程宏的同一位置注册所有属性。假设我们想添加两个属性(我们仍然不讨论它们属于什么:结构或字段,或者两者):

    之后,您可能已经使用以下内容编译了用户代码:

     #[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\u change
    不属于任何已知属性

    这些属性是可选的,我们所做的只是允许指定它们。在派生过程宏时,您可以检查所需的所有内容(包括属性是否存在)和
    panic在某些条件下,编译器将告诉您有意义的消息

  • 现在我们将讨论如何处理该属性!让我们忘记
    state\u transitions
    属性,因为它的处理与处理struct/enum属性没有太大区别(实际上它只是多一点代码),而讨论
    state\u change
    syn
    板条箱为您提供了有关定义的所有必要信息(但不幸的是没有实现(我在这里谈论的是
    impl
    ),但这对于处理属性来说已经足够了)。更详细地说,我们需要,,
    syn::Attribute
    以及最后

  • 要处理字段的属性,需要从一个到另一个遍历所有这些结构。当您到达
    Vec
    -这是您想要的,一个字段的所有属性的列表。在这里可以找到我们的状态转换。当您找到它时,您可能想要获取它的内容,这可以通过使用匹配的
    syn::MetaItem
    enum来完成。只需阅读文档:)下面是一个简单的示例代码,当我们在某个字段上发现
    state\u change
    属性,并检查目标实体是否派生
    Copy
    Clone
    或两者都不派生时,代码会恐慌:

        #[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()
        }
    
    您可能对阅读感兴趣。最后一个链接实际上是我自己的项目,向我自己解释如何在Rust中使用过程宏



    经过1.15版本的升级和syn机箱的更新后,无法再检查
    enums/structs
    的派生,但是,其他一切都正常。

    您能提供一个简单的示例说明如何执行此操作吗?我看到了您提到的代码,但是也实现了很多东西。如果您使用的是
    syn
    ,那么您可以通过访问
    字段
    结构的
    attr
    字段来访问字段的属性。通过检查的
    syn::MacroInput
    body
    字段,然后调用以获取
    字段的列表,可以获得
    字段的结构
    serde\u codegen\u internal
    链接现在无效。如果不确定要指向何处,则会进行编辑。另外,谢谢你的回答,非常有帮助:)@Mihir我刚刚更新了链接,指向了与之前指向的内容类似的内容。就我所记得的,我指的是serde的代码,他们在那里处理过程宏,这个新链接指向他们的新家。自从给出这个答案以来,这个项目已经有了很大的发展,所以早期阅读和理解他们的代码要容易得多,但现在它是一个巨大的代码块,可能不如初学者的阅读那么好。我相信,仍然有一些很好的例子。我会尝试添加链接到我能找到的东西,以及在一分钟内。
     #[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,
     }
    
        #[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()
        }