如何从宏创建参数化类型?
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
类型传递(就像 fn
、enum
、use
、trait
、&c.会做)。
我有一个宏,它创建一个结构和一堆支持函数和特征实现。这个问题的有趣之处在于:
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
类型传递(就像 fn
、enum
、use
、trait
、&c.会做)。