首先,在使用
macro_rules!
解析泛型的时候,要做到完全无误几乎是不可能的(甚至可能是不可能的),因为模式不支持混合重复(例如
$( $( $lt:lifetime ) | $( $gen:ident )* )*
,它将匹配生命周期(
'a
)或者一个泛型参数(
T
))。
如果确实需要这样做,可以考虑使用
proc-macro
(使用
proc-macro-hack
可以将它们甚至放在表达式位置)。
简单地在此处放置代码而不加说明对任何人都没有好处,因此以下步骤将详细介绍理解最终声明性宏所需的所有步骤 :)
以
Hello<'a, 'b>
或
Hello
的形式解析输入相对较简单:
macro_rules! simple_match {
(
$name:ident
$(<
$( $lt:lifetime ),+
>)?
) => {}
}
simple_match!( Hello<'a, 'b, 'static> );
您可能还有受限生命周期(例如Hello<'a, 'b: 'a, 'static>
),这不能用以上方法解析。
为了解析这个,需要在$lt:lifetime
的末尾添加以下模式:
$( : $clt:lifetime )?
macro_rules! better_match {
(
$name:ident
$(<
$(
$lt:lifetime
$( : $clt:lifetime )?
),+
>)?
) => {}
}
better_match!( Hello<'a, 'b: 'static> );
上述限制仅适用于单个受限生命周期(
Hello<'a: 'b + 'c>
将无法解析)。为了支持多个受限生命周期,必须更改模式为:
$(
: $clt:lifetime
$(+ $dlt:lifetime )*
)?
这就是解析通用生命周期所需的全部内容。也可以尝试解析更高级别的生命周期,但这会使模式变得更加复杂。
因此,解析生命周期的最终宏看起来像这样:
macro_rules! lifetimes {
( $name:ident $(< $( $lt:lifetime $( : $clt:lifetime $(+ $dlt:lifetime )* )? ),+ >)? ) => {}
}
lifetimes!( Hello<'b, 'a: 'b, 'static, 'c: 'a + 'b> );
上述宏仅允许生命周期,可以通过在模式中将lifetime
替换为tt
来修复此问题(生命周期和通用参数都可以解析为tt
):
macro_rules! generic {
( $name:ident $(< $( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+ >)? ) => {}
}
generic!( Hello<'b, 'a: 'b, 'static, 'c: 'a + 'b> );
generic!( Hello<T: Display, D: Debug + 'static + Display, 'c: 'a + 'b> );
如我之前提到的,目前似乎无法区分lifetime和trait bound。如果需要区分,可以部分地使用($(+ $lt:lifetime)* $(+$param:ident)*)
,但这对于类似于Hello<'a, T, 'b>
或T: 'a + Debug + 'c
这样的未排序边界是不起作用的。
impl_trait
宏将会像这样编写:
use std::fmt::{Debug, Display};
trait ExampleTrait {}
struct Alpha;
struct Beta<'b>(&'b usize);
struct Gamma<T>(T);
struct Delta<'b, 'a: 'static + 'b, T: 'a, D: Debug + Display + 'a> {
hello: &'a T,
what: &'b D,
}
macro_rules! impl_trait {
( $name:ident $(< $( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+ >)? ) => {
impl $(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)?
ExampleTrait
for $name
$(< $( $lt ),+ >)?
{}
}
}
impl_trait!(Alpha);
impl_trait!(Beta<'b>);
impl_trait!(Gamma<T>);
impl_trait!(Delta<'b, 'a: 'static + 'b, T: 'a, D: Debug + Display + 'a>);
注意: 不支持路径(例如
impl_trait!(Hello<D: std::fmt::Display>)
。
下面的宏可与多个结构体一起使用。
macro_rules! impl_trait_all {
( $( $name:ident $(< $( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+ >)? ),+ ) => {
$(
impl $(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)?
ExampleTrait
for $name
$(< $( $lt ),+ >)?
{}
)+
}
}
impl_trait_all!(
Alpha,
Beta<'b>,
Gamma<T>,
Delta<'b, 'a: 'static + 'b, T: 'a, D: Debug + Display + 'a>
);
Link to playground with all the code