我有一个宏,它创建一个结构和一堆支持函数和特征实现。这个问题的有趣之处在于:
macro_rules! make_struct {
($name: ident) => {
struct $name;
}
}
这可以按你所期望的那样工作:
make_struct!(MyStruct);
但是,如果我想制作参数化类型,我运气不好:
make_struct!(AnotherStruct<T: SomeTrait>);
test.rs:8:27: 8:28 error: no rules expected the token `<`
test.rs:8 make_struct!(AnotherStruct<T: SomeTrait>);
结构的名称是ident
所以我不能在宏args中改变它(例如改为ty
):
test.rs:3:16: 3:21 error: expected ident, found `MyStruct`
test.rs:3 struct $name;
那么如何编写这个宏才能处理这两个?或者我需要分开吗?在后一种情况下,宏看起来像什么?
答案 0 :(得分:4)
在关键字struct
之后,解析器需要一个ident标记树,后面跟着<
等等; ty绝对不是它想要的(作为为什么它不起作用的一个例子,(Trait + Send + 'static)
是一个有效的ty,但struct (Trait + Send + 'static);
显然没有意义。)
要支持泛型,您需要制定更多规则。
macro_rules! make_struct {
($name:ident) => {
struct $name;
};
($name:ident<$($t:ident: $constraint:ident),+>) => {
struct $name<$($t: $constraint),+>;
}
}
正如你无疑观察到的那样,让它支持解析器在该位置接受的所有内容几乎是不可能的; macro_rules不是那么聪明。然而,有一个奇怪的技巧(静态代码分析器讨厌它!),它允许你采用一系列令牌树并将其视为正常的struct
定义。它只使用了一些与macro_rules间接的方法:
macro_rules! item {
($item:item) => ($item);
}
macro_rules! make_struct {
($name:ident) => {
struct $name;
};
($name:ident<$($tt:tt)*) => {
item!(struct $name<$($tt)*;);
};
}
请注意,由于过度热情,ident
目前不允许重复序列重复($(…)
),因此您将不得不在ident
之间放置一些令牌树和重复,例如$name:ident, $($tt:tt)*
{屈服AnotherStruct, <T: SomeTrait>
)或$name:ident<$(tt:tt)*
=&gt; struct $name<$($tt)*;
。由于你不能轻易地将它分开以获得单独的泛型类型,因此你需要做的事情就是插入PhantomData
标记。
您可能会发现传递整个struct
项很有帮助;它以item
类型传递(就像fn
,enum
,use
,trait
,&amp; c。做)。