我有一个实现特征的宏impl_Trait!()
。目前,它适用于没有通用参数的类型,但我不确定如何将类型参数添加到impl
关键字。
macro_rules! impl_FooTrait {
($name:ty) => {
impl $crate::FooTrait for $name { ... }
};
}
struct Bar(i32);
impl_FooTrait!(Bar);
// All OK
struct Baz<'a>(&'a i32);
impl_FooTrait!(Baz<'a>);
// use of undeclared lifetime name `'a`
答案 0 :(得分:6)
以免责声明的方式提交此答案:可能有更好的方法可以做到这一点。我还不熟悉宏观之地。
您可以使用tt
(单个令牌)标识符来接受您想要在另一个宏臂中使用的生命周期(playground link)
macro_rules! impl_FooTrait {
($name:ty, $lifetime:tt) => {
impl<$lifetime> $crate::FooTrait for $name { }
};
($name:ty) => {
impl $crate::FooTrait for $name { }
};
}
struct Bar(i32);
impl_FooTrait!(Bar);
struct Baz<'a>(&'a i32);
impl_FooTrait!(Baz<'a>, 'a); // Use and declare the lifetime during macro invocation
我觉得看起来有点奇怪。我有兴趣看到任何其他有其他选择的答案。
这是一个实际实现某些内容的示例:Playground link
答案 1 :(得分:2)
首先,以简单的方式使用macro_rules!
解析泛型非常困难(可能是不可能的),因为模式不支持混合重复
(例如$( $( $lt:lifetime ) | $( $gen:ident )* )*
,它将与生存期('a
)或通用参数(T
匹配)。
如果需要这样做,您应该考虑使用proc-macro
(甚至可以使用proc-macro-hack
将它们放在表达式位置)。
仅将代码放在此处而不作任何解释不会使任何人受益,因此下面将介绍理解最终声明性宏所需的所有步骤:)
解析Hello<'a, 'b>
或Hello
形式的输入相对简单:
macro_rules! simple_match {
(
// name of the struct/enum
$name:ident
// only one or none `<>`
$(<
// match one or more lifetimes separated by a comma
$( $lt:lifetime ),+
>)?
) => {}
}
simple_match!( Hello<'a, 'b, 'static> );
一个生命周期也可能受约束(例如Hello<'a, 'b: 'a, 'static>
),而上述生命周期则无法解析。
也要解析这个问题,必须在$lt:lifetime
的末尾添加以下模式:
// optional constraint: 'a: 'b
$( : $clt:lifetime )?
macro_rules! better_match {
(
// name of the struct/enum
$name:ident
// only one or none `<>`
$(<
// match one or more lifetimes separated by a comma
$(
$lt:lifetime
// optional constraint: 'a: 'b
$( : $clt:lifetime )?
),+
>)?
) => {}
}
better_match!( Hello<'a, 'b: 'static> );
以上内容仅限于一个受约束的生存期(Hello<'a: 'b + 'c>
将无法解析)。为了支持多个受限寿命,必须将模式更改为:
$(
: $clt:lifetime
// allow `'z: 'a + 'b + 'c`
$(+ $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
):< / p>
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> );
就像我上面提到的,我认为目前无法区分一生和特质。如果需要这样做,可以使用( $(+ $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 )* )? ),+ >)? ) => {
// I split this over multiple lines to make it more readable...
// this is essentially just a copy of the above match without the
// type annotations
impl $(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)?
ExampleTrait
for $name
// the bounds are not required here
$(< $( $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 )* )? ),+ >)? ),+ ) => {
$(
// I split this over multiple lines to make it more readable...
// this is essentially just a copy of the above match without the
// type annotations
impl $(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)?
ExampleTrait
for $name
// the bounds are not required here
$(< $( $lt ),+ >)?
{}
)+
}
}
impl_trait_all!(
Alpha,
Beta<'b>,
Gamma<T>,
Delta<'b, 'a: 'static + 'b, T: 'a, D: Debug + Display + 'a>
);
答案 2 :(得分:1)
这是一个古老的问题,但是这里似乎仍然没有很好的答案。我有一个部分解决方案,尽管它匹配一些不正确的输入,并且我无法使其适用于生命周期参数。
#[macro_export]
macro_rules! impl_trait {
// this evil monstrosity matches <A, B: T, C: S+T>
// but because there is no "zero or one" rule, also <D: S: T>
($ty:ident < $( $N:ident $(: $b0:ident $(+$b:ident)* )* ),* >) =>
{
impl< $( $N $(: $b0 $(+$b)* )* ),* >
$crate::path::to::Trait
for $ty< $( $N ),* >
{
// function implementations go here
}
};
// match when no type parameters are present
($ty:ident) => {
impl_trait!($ty<>);
};
}