#递归 #过程宏 #宏 derive #自动 #实用工具

derive_recursive

用于递归特质实现的实用工具 derive 宏

4 个稳定版本

1.0.3 2024年4月18日
1.0.2 2024年4月17日
1.0.1 2024年4月10日
1.0.0 2024年4月7日

1026过程宏 中排名

Download history 222/week @ 2024-04-12 63/week @ 2024-04-19 71/week @ 2024-07-26

71 每月下载量
3 个crate中使用(通过 geo-aid-internal

MIT 许可证

38KB
670 代码行

适用于所有递归特质的单个 derive 宏

此 proc-macro 特质引入了一个特殊的 derive 宏,称为 Recursive,可用于自动生成大多数您将要编写的特质的递归实现。

使用 Recursive

与结构体

让我们看看以下特质

trait Count {
    /// Sums up all integer values in the data.
    fn count(&self) -> i32;
}

impl<T: Count> Count for Vec<T> {
    fn count(&self) -> i32 {
        self.iter().map(T::count).sum()
    }
}

impl Count for i32 {
    fn count(&self) -> i32 {
        *self
    }
}

在这里,count 函数应将数据中的所有 i32 值相加。假设我们想为以下结构体实现它

struct Foo {
    bar: i32,
    zee: Vec<i32>
}

首先,我们必须为我们的类型 derive Recursive(并导入宏)

use derive_recursive::Recursive;

// ...

#[derive(Recursive)]
struct Foo {
    bar: i32,
    zee: Vec<i32>
}

但这本身不会给我们带来任何东西。为了实际上生成一些代码,我们需要告诉库要实现哪些特质以及如何实现它们的函数。我们使用 recursive 辅助宏来完成此操作

#[derive(Recursive)]
#[recursive(
    impl Count for Self {
        fn count(&self) -> i32 {
            aggregate = +
        }
    }
)]
struct Foo {
    bar: i32,
    zee: Vec<i32>
}

这段代码非常多,但正如您将看到的,它实际上非常简单。首先,我们通过一个标准的 impl 块来指定我们想要实现哪个特征。请注意,这确实是一个标准的 impl 块,因为它也以相同的方式接受泛型。也就是说,如果特性和 self 类型有任何泛型,您会像实际实现特征一样写它们。与标准 impl 块的唯一区别是,必须强制使用 Self,而不是一个路径。这里的 Self 仅仅是一个类型名称的替代,而不是泛型。任何泛型都必须实际给出。当我们完成特征的指定后,我们指定我们想要实现哪些函数。我们必须编写所有必需(和可能可选)函数的完整签名,因为 derive_recursive 对它所实现的特征一无所知。签名写法完全就像它在实际的 impl 块中一样。然后,在通常函数代码会放置的地方,我们放入实现参数。

实现参数是我们指定如何具体处理函数的方式。在这里,我们使用了 aggregate 来告诉 Recursive 使用 + 运算符合并 Foo 字段上函数调用的结果。aggregate 参数也接受 *&|&&|| 作为运算符,_ 作为特殊 unit 合并,它只返回最后一个递归调用的结果,以及用于自定义合并的函数路径。这适用于关联函数(字段类型)。aggregate 参数还可以是表示构造函数的 {}。调用结果随后组合成一个 Self 类型的返回值。如果没有提供 aggregate 参数,则仅处理结构体中的第一个字段。值得注意的是,递归函数的任何参数都会传递给后续的调用。

最终,它展开成以下内容

impl Count for Foo {
    fn count(&self) -> i32 {
        let Self { bar: f0, zee: f1 } = self;
        { <i32 as Count>::count(f0) + <Vec<i32> as Count>::count(f1) }
    }
}

如果您想以这种方式为 Foo 实现另一个特征,则需要一个新的 recursive 辅助宏。下面的示例展示了上面提到的几个概念

#[derive(Recursive)]
#[recursive(
    impl<C: Count> Count for Self<C> {
        fn count(&self) -> i32 {
            aggregate = +
        }
    }
)]
#[recursive(
    impl<C, T> Bar<T> for Self<C> {
        fn bar(x: C) -> Self {
            aggregate = {}
        }
    } 
)]
struct Foo<C> {
    bar: C,
    zee: Vec<i32>
}

这会展开成

impl<C: Count> Count for Foo<C> {
    fn count(&self) -> i32 {
        let Self { bar: f0, zee: f1 } = self;
        { <C as Count>::count(f0) + <Vec<i32> as Count>::count(f1) }
    }
}
impl<C, T> Bar<T> for Foo<C> {
    fn bar(arg0: C) -> Self {
        Self {
            bar: <C as Bar<T>>::bar(arg0),
            zee: <Vec<i32> as Bar<T>>::bar(arg0),
        }
    }
}

受实现函数影响的字段也可以使用 marker 属性进行过滤

#[derive(Recursive)]
#[recursive(
    impl Bar for Self {
        fn modify(&mut self) {
            aggregate = _,
            marker = only_this
        }
    }
)]
struct Foo {
    a: String,
    b: u32,
    c: Vec<&'static str>
}

然后我们可以使用 #[recursive(<marker>)] 标记正确的字段

struct Foo {
    #[recursive(only_this)]
    a: String,
    b: u32,
    #[recursive(only_this)]
    c: Vec<&'static str>
}

这会展开成以下内容

impl Bar for Foo {
    fn modify(&mut self) {
        let Self { a: f0, b: f1, c: f2 } = self;
        {
            <String as Bar>::modify(f0);
            <Vec<&'static str> as Bar>::modify(f2)
        }
    }
}

如果没有提供 aggregate,则第一个找到的正确标记的字段会受到影响的。当然,您可以为同一字段添加多个标记。值得一提的是,如果 aggregate 是 {},则 marker 属性无效。构造必须影响所有字段。

另一个有用的属性是 wrap 属性。它可以有两个不同的值:ResultOption。如果存在该属性,所有递归函数调用都将跟一个 ?,并且最终返回值将被包裹到相应的变体中(OkSome)。

与枚举一起使用

recursive_derives 也可以与枚举一起工作。上述所有属性仍然适用,而 markeraggregate 会修改变体的处理方式。对于与枚举变体的交互(聚合和过滤),我们使用 variant_aggregatevariant_markervariant_aggregate 只允许与非构造性(attribute 不是 {})关联的函数一起使用,因为在函数接收者时无法进行聚合。属性决定了递归应用函数到变体上产生的结果是如何聚合的。它接受 aggregate 属性接受的任何值,除了 {}variant_marker 可以用来过滤受影响的变体。它仅允许在关联函数上使用,在关联构造函数(aggregate{})上是必需的。它的工作方式与 marker 属性完全相同,只不过这次,我们将属性应用到变体本身。

以下示例展示了从标准库中派生 DefaultClone 特性以及我们自己的 Size 特性,确定实现者的内存大小

#[derive(Recursive)]
#[recursive(
    impl Default for Self {
        fn default() -> Self {
            aggregate = {},
            variant_marker = default
        }
    }
)]
#[recursive(
    impl Clone for Self {
        fn clone(&self) -> Self {
            aggregate = {}
        }
    }
)]
#[recursive(
    impl Size for Self {
        fn size() -> usize {
            aggregate = +,
            variant_aggregate = max,
        }
    }
)]
enum Foo {
    #[recursive(default)]
    Bar(String, usize),
    Vee {
        a: Zee,
        b: Vec<u8>
    }
}

这会展开成

impl Default for Foo {
    fn default() -> Self {
        Self::Bar(<String as Default>::default(), <usize as Default>::default())
    }
}
impl Clone for Foo {
    fn clone(&self) -> Self {
        match self {
            Self::Bar(f0, f1) => {
                Self::Bar(<String as Clone>::clone(f0), <usize as Clone>::clone(f1))
            }
            Self::Vee { a: f0, b: f1 } => {
                Self::Vee {
                    a: <Zee as Clone>::clone(f0),
                    b: <Vec<u8> as Clone>::clone(f1),
                }
            }
        }
    }
}
impl Size for Foo {
    fn size() -> usize {
        max(
            { <String as Size>::size() + <usize as Size>::size() },
            { <Zee as Size>::size() + <Vec<u8> as Size>::size() },
        )
    }
}

附加属性

initvariant_init

您可以使用 init 属性(接受一个表达式)来添加一个特殊的 init 表达式,该表达式将与后续的递归调用一起聚合。init 表达式始终是第一个执行的。当使用 variant_init 聚合枚举变体时,可以实现类似的结果。

override_marker

override_marker 属性可以用来为特定变体/字段添加一个自定义函数来替代递归调用。同一个属性可以用于两者。变体和字段应该像其他标记属性一样标记,只有一个变化

#[recursive(marker_name = func_here)]
struct Foo;

语句的右侧应该是一个返回函数的表达式(这意味着它还允许闭包)。

免责声明

请注意,这个库相当新,测试不多,在某些方面有限制(最值得注意的是,对单元结构和变体的支持非常有限)。然而,它应该足够适用于大多数用例。如果您发现了一个错误或者有改进库的想法(包括您需要但库没有提供的东西),请随时提出问题(如果您有时间,也许您自己可以做出改变并添加一个 PR)。请注意,我不会在库上投入很多精力,因为我有其他更重要项目。

依赖关系

~275–720KB
~17K SLoC