macros - 是否可以让宏扩展到结构字段?
问题描述
我想执行以下操作,但该位置的宏似乎不起作用(我得到了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> });
解决方案
实际上,宏调用在结构定义的中间是无效的。但是,我们可以在那里使用元变量。诀窍是逐步解析参数,一路为字段定义构建标记,当没有更多输入要处理时,发出一个结构定义,其中字段定义来自元变量。
作为第一步,让我们看看不处理字段类型的宏具体是什么样的:
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,)* } -> ());
);
}
这个宏唯一做的就是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)*,)* } -> ());
);
}
请注意,最后一条规则有所不同:ty
我们现在不是匹配 a ,而是匹配tt
. 那是因为一旦宏解析了 a ty
,它就不能被分解,所以当我们进行递归宏调用时, aty
不可能匹配类似的东西Option<$type:ty>
。
推荐阅读
- c++ - 使用 QtAudioOutput 处理大型 QByteArray 会导致 std::bad_alloc
- apache-camel - 在类路径中找不到属性文件 application.properties
- php - 按条件突出显示行
- html - HTML/CSS @media Query : 该行不断消失
- javascript - Firestore 数组包含的位置,如果为空则忽略
- navigation - 在导航地图 MAPBOX ANDROID 中旋转用户的三角形
- asp.net - 请求被中止
- django - 在 Django 中动态连接到数据库
- python - 如何在 python 中进行全局导入?
- php - php在树枝代码中转换多维数组