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

时间:2015-06-02 06:39:26

标签: templates generics rust

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

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;

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

1 个答案:

答案 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类型传递(就像fnenumusetrait&amp; c。做)。