是否可以让宏扩展为结构字段?

fly*_*eep 5 macros rust

我想执行以下操作,但该位置的宏似乎不起作用(我得到error: expected `:`, found `!`. 如何模式匹配单个结构成员并根据匹配将属性附加到它们?

use serde_derive::Serialize;

macro_rules! optional_param {
    ($name:ident : Option<$type:ty>) => { #[serde(skip_serializing_if = "Option::is_none")] pub $name: Option<$ty> };
    ($name:ident : Vec   <$type:ty>) => { #[serde(skip_serializing_if = "Vec::is_empty"  )] pub $name: Vec   <$ty> };
    ($name:ident : bool            ) => { #[serde(skip_serializing_if = "bool::not"      )] pub $name: bool        };
}

macro_rules! impl_extra {
    ( $name:ident { $( $param:ident : $type:ty ),* $(,)* } ) => (
        #[derive(Default,Debug,Serialize)]
        pub struct $name {
            $( optional_param!($param : $type), )*
        }
    );
}

impl_extra!(MyStruct { member: Option<String> });
Run Code Online (Sandbox Code Playgroud)

链接到游乐场

Fra*_*gné 5

事实上,宏调用在结构定义的中间是无效的。但是,我们可以在那里使用元变量。诀窍是逐步解析参数,在此过程中为字段定义构建标记,并且当没有更多输入要处理时,发出一个结构定义,其中字段定义来自元变量。

作为第一步,让我们看看一个不专门处理字段类型的宏是什么样的:

macro_rules! impl_extra {
    ( @ $name:ident { } -> ($($result:tt)*) ) => (
        #[derive(Default, Debug, Serialize)]
        pub struct $name {
            $($result)*
        }
    );

    ( @ $name:ident { $param:ident : $type:ty, $($rest:tt)* } -> ($($result:tt)*) ) => (
        impl_extra!(@ $name { $($rest)* } -> (
            $($result)*
            pub $param : $type,
        ));
    );

    ( $name:ident { $( $param:ident : $type:ty ),* $(,)* } ) => (
        impl_extra!(@ $name { $($param : $type,)* } -> ());
    );
}
Run Code Online (Sandbox Code Playgroud)

这个宏唯一能做的就是pub在每个字段上添加并定义pub struct一个#[derive]属性。第一条规则处理终端情况,即当没有更多字段需要处理时。第二条规则处理递归情况,第三条规则处理宏的“公共”语法并将其转换为“处理”语法。

请注意,我使用 an@作为内部规则的初始标记,以将它们与“公共”规则区分开来。如果此宏不打算导出到其他 crate,那么您还可以将内部规则移动到不同的宏。如果宏被导出,那么内部规则的单独宏可能也必须被导出。

现在,让我们处理各种字段类型:

macro_rules! impl_extra {
    ( @ $name:ident { } -> ($($result:tt)*) ) => (
        #[derive(Default, Debug, Serialize)]
        pub struct $name {
            $($result)*
        }
    );

    ( @ $name:ident { $param:ident : Option<$type:ty>, $($rest:tt)* } -> ($($result:tt)*) ) => (
        impl_extra!(@ $name { $($rest)* } -> (
            $($result)*
            #[serde(skip_serializing_if = "Option::is_none")]
            pub $param : Option<$type>,
        ));
    );

    ( @ $name:ident { $param:ident : Vec<$type:ty>, $($rest:tt)* } -> ($($result:tt)*) ) => (
        impl_extra!(@ $name { $($rest)* } -> (
            $($result)*
            #[serde(skip_serializing_if = "Vec::is_empty")]
            pub $param : Vec<$type>,
        ));
    );

    ( @ $name:ident { $param:ident : bool, $($rest:tt)* } -> ($($result:tt)*) ) => (
        impl_extra!(@ $name { $($rest)* } -> (
            $($result)*
            #[serde(skip_serializing_if = "bool::not")]
            pub $param : bool,
        ));
    );

    ( $name:ident { $( $param:ident : $($type:tt)* ),* $(,)* } ) => (
        impl_extra!(@ $name { $($param : $($type)*,)* } -> ());
    );
}
Run Code Online (Sandbox Code Playgroud)

请注意,最后一条规则有所不同:ty我们现在匹配 的序列,而不是匹配 a tt。那是因为一旦宏解析了 a ty,它就无法分解,所以当我们进行递归宏调用时, aty不可能匹配Option<$type:ty>.