如何从宏创建参数化类型?

Rob*_*b N 11 generics templates rust

我有一个宏,它创建一个结构和一堆支持函数和特征实现.这个问题的有趣之处在于:

macro_rules! make_struct {
    ($name: ident) => {
        struct $name;
    }
}
Run Code Online (Sandbox Code Playgroud)

这可以按照您的期望工作:

make_struct!(MyStruct);
Run Code Online (Sandbox Code Playgroud)

如果我想制作参数化类型,我运气不好:

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>);
Run Code Online (Sandbox Code Playgroud)

结构的名称是一个ident所以我不能只在宏args中更改它(例如to ty):

test.rs:3:16: 3:21 error: expected ident, found `MyStruct`
test.rs:3         struct $name;
Run Code Online (Sandbox Code Playgroud)

那么如何编写这个宏来处理这两个呢?或者我需要分开吗?在后一种情况下,宏看起来像什么?

Chr*_*gan 5

在关键字 之后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),+>;
    }
}
Run Code Online (Sandbox Code Playgroud)

正如您肯定观察到的那样,让它支持解析器在该位置接受的所有内容几乎是不可能的;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)*;);
    };
}
Run Code Online (Sandbox Code Playgroud)

请注意,由于过度热心,ident当前不允许序列重复 ( $(…)) 立即跟随它,因此您将被困在ident和 重复之间放置一些标记树,例如$name:ident, $($tt:tt)*(yielding AnotherStruct, <T: SomeTrait>) 或$name:ident<$(tt:tt)*=> struct $name<$($tt)*;。由于您无法如此轻松地将其拆开以获取单个泛型类型,因此它的价值降低了,您需要为插入PhantomData标记之类的事情做这些。

您可能会发现传递整个struct项目很有帮助;它作为item类型传递(就像 a fn, enum, use, trait, &c.会做的那样)。