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

How do I create a parameterised type from a macro?

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

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 所以我不能只在宏参数中更改它(例如更改为 ty):

test.rs:3:16: 3:21 error: expected ident, found `MyStruct`
test.rs:3         struct $name;

那么我该如何编写这个宏才能同时处理这两个问题呢?或者我需要分开吗?在后一种情况下,宏是什么样的?

在关键字struct之后,解析器需要一个ident token树,其后可能跟<等等; 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 目前不允许序列重复 ($(…)) 紧随其后,因此您将无法在 [=17 之间放置一些标记树=] 和重复,例如$name:ident, $($tt:tt)*(产生 AnotherStruct, <T: SomeTrait>)或 $name:ident<$(tt:tt)* => struct $name<$($tt)*;。 this 的价值降低了,因为你不能很容易地把它拆开来得到单独的泛型类型,你需要为插入 PhantomData 标记这样的事情做。

您可能会发现传递整个 struct 项很有帮助;它作为 item 类型传递(就像 fnenumusetrait&c.会做)。