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 在 过程宏 中排名
71 每月下载量
在 3 个crate中使用(通过 geo-aid-internal)
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
属性。它可以有两个不同的值:Result
和 Option
。如果存在该属性,所有递归函数调用都将跟一个 ?
,并且最终返回值将被包裹到相应的变体中(Ok
或 Some
)。
与枚举一起使用
recursive_derives
也可以与枚举一起工作。上述所有属性仍然适用,而 marker
和 aggregate
会修改变体的处理方式。对于与枚举变体的交互(聚合和过滤),我们使用 variant_aggregate
和 variant_marker
。 variant_aggregate
只允许与非构造性(attribute
不是 {}
)关联的函数一起使用,因为在函数接收者时无法进行聚合。属性决定了递归应用函数到变体上产生的结果是如何聚合的。它接受 aggregate
属性接受的任何值,除了 {}
。 variant_marker
可以用来过滤受影响的变体。它仅允许在关联函数上使用,在关联构造函数(aggregate
是 {}
)上是必需的。它的工作方式与 marker
属性完全相同,只不过这次,我们将属性应用到变体本身。
以下示例展示了从标准库中派生 Default
和 Clone
特性以及我们自己的 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() },
)
}
}
附加属性
init
和 variant_init
您可以使用 init
属性(接受一个表达式)来添加一个特殊的 init 表达式,该表达式将与后续的递归调用一起聚合。init 表达式始终是第一个执行的。当使用 variant_init
聚合枚举变体时,可以实现类似的结果。
override_marker
override_marker
属性可以用来为特定变体/字段添加一个自定义函数来替代递归调用。同一个属性可以用于两者。变体和字段应该像其他标记属性一样标记,只有一个变化
#[recursive(marker_name = func_here)]
struct Foo;
语句的右侧应该是一个返回函数的表达式(这意味着它还允许闭包)。
免责声明
请注意,这个库相当新,测试不多,在某些方面有限制(最值得注意的是,对单元结构和变体的支持非常有限)。然而,它应该足够适用于大多数用例。如果您发现了一个错误或者有改进库的想法(包括您需要但库没有提供的东西),请随时提出问题(如果您有时间,也许您自己可以做出改变并添加一个 PR)。请注意,我不会在库上投入很多精力,因为我有其他更重要项目。
依赖关系
~275–720KB
~17K SLoC