【问题标题】:Rust macro accepting type with generic parametersRust 宏接受具有泛型参数的类型
【发布时间】:2017-01-12 00:54:30
【问题描述】:

我有一个实现特征的宏,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`

【问题讨论】:

  • 我不是宏观专家(我真的很想深入研究它们),但here is one option you could try。看起来有点奇怪..使用生命周期然后声明它。不过它似乎有效。

标签: generics macros rust


【解决方案1】:

您可以使用 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

这里是an example that actually implements something

我猜看起来有点奇怪。我有兴趣看到任何替代答案。可能有更好的方法来做到这一点;我还不太熟悉宏观领域。

【讨论】:

  • 如果将$lifetime:tt 替换为$($args:tt)*,它将适用于任意数量的通用参数。 例如 impl_FooTrait!(Qux&lt;'a, T&gt;, 'a, T: 'a);
  • 另外,is 是一种更简洁的look 方式...但是,坦率地说,如果没有过程宏,这太荒谬了,事实并非如此值得进入这里。如果你很好奇,请查看parse-generics-shim crate 的来源,尽量不要发疯。
  • 哦,太好了。谢谢@DK,我会检查一下!
【解决方案2】:

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

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

简单地把代码放在这里没有任何解释不会使任何人受益,所以下面将介绍理解最终声明性宏所需的所有步骤:)


解析Hello&lt;'a, 'b&gt;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&lt;'a, 'b: 'a, 'static&gt;),不能用上面的方法解析。

为了也解析它,必须将以下模式添加到 $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&lt;'a: 'b + 'c&gt; 将无法解析)。为了支持多个受约束的生命周期,必须将模式更改为:

$(
    : $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):

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&lt;'a, T, 'b&gt;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>);

注意:不支持路径(例如impl_trait!(Hello&lt;D: std::fmt::Display&gt;)


以下宏在调用中与多个结构一起使用:

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>
);

Link to playground with all the code

【讨论】:

    【解决方案3】:

    我有一个部分解决方案,但我无法使其适用于生命周期参数。

    #[macro_export]
    macro_rules! impl_trait {
        // this evil monstrosity matches <A, B: T, C: 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<>);
        };
    }
    

    Example (play)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多