Rust宏接受类型与通用参数

Nat*_*ngo 6 generics macros rust

我有一个实现特征的宏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`
Run Code Online (Sandbox Code Playgroud)

小智 7

首先,macro_rules!以万无一失的方式解析泛型非常困难(可能是不可能的),因为模式不支持混合重复(例如$( $( $lt:lifetime ) | $( $gen:ident )* )*,匹配生命周期 ( 'a) 或泛型参数 ( T) )。

如果需要,您应该考虑使用 a proc-macro(您甚至可以使用将它们放在表达式位置proc-macro-hack)。

简单地将代码不加解释地放在这里对任何人都没有好处,所以下面介绍了理解最终声明性宏所需的所有步骤:)


Hello<'a, 'b>or的形式解析输入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> );
Run Code Online (Sandbox Code Playgroud)

一个人可能也有受限制的生命周期(例如Hello<'a, 'b: 'a, 'static>),这不能用上面的方法解析。

要对此进行解析,必须将以下模式添加到 的末尾$lt:lifetime

// optional constraint: 'a: 'b
$( : $clt:lifetime )?
Run Code Online (Sandbox Code Playgroud)
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> );
Run Code Online (Sandbox Code Playgroud)

以上仅限于单个受约束的生命周期(Hello<'a: 'b + 'c>将无法解析)。为了支持多个受约束的生命周期,必须将模式更改为:

$(
    : $clt:lifetime
    // allow `'z: 'a + 'b + 'c`
    $(+ $dlt:lifetime )*
)?
Run Code Online (Sandbox Code Playgroud)

这就是解析通用生命周期所需的一切。也可以尝试解析排名较高的生命周期,但这会使模式更加复杂。

所以解析生命周期的最终宏看起来像这样

macro_rules! lifetimes {
    ( $name:ident $(< $( $lt:lifetime $( : $clt:lifetime $(+ $dlt:lifetime )* )? ),+ >)? ) => {}
}

lifetimes!( Hello<'b, 'a: 'b, 'static, 'c: 'a + 'b> );
Run Code Online (Sandbox Code Playgroud)

上面的宏只允许生命周期,可以通过在模式中替换lifetimewith来修复tt(生命周期和通用参数都可以解析为 a 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> );
Run Code Online (Sandbox Code Playgroud)

就像我上面提到的,我认为目前无法区分终身和特质界限。如果需要这样做,可以部分使用( $(+ $lt:lifetime )* $(+ $param:ident )* ),但这不适用于未排序的边界,如Hello<'a, T, 'b>T: 'a + Debug + 'c


然后impl_trait-macro 会这样写:

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>);
Run Code Online (Sandbox Code Playgroud)

注意:不支持路径(例如。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>
);
Run Code Online (Sandbox Code Playgroud)

链接到带有所有代码的操场


Sim*_*ead 6

以免责声明的方式提交此答案:可能有更好的方法可以做到这一点.我还不熟悉宏观的土地.

你可以使用tt(单个标记)标识符来接受你想要的另一个宏臂(游乐场链接)的生命周期

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
Run Code Online (Sandbox Code Playgroud)

看起来我觉得有点奇怪.我有兴趣看到任何其他有替代品的答案.

这是一个实际实现的例子:Playground链接

  • 如果将$ lifetime:tt替换为$($ args:tt)*`,它将适用于任意数量的泛型参数。* eg *`impl_FooTrait!(Qux &lt;'a,T&gt;,'a,T:'a);` (2认同)