4 stable releases
1.0.3 | Apr 18, 2024 |
---|---|
1.0.2 | Apr 17, 2024 |
1.0.1 | Apr 10, 2024 |
1.0.0 | Apr 7, 2024 |
#1432 in Procedural macros
26 downloads per month
Used in 4 crates
(via geo-aid-script)
38KB
670 lines
A single derive macro for all your recursive traits
This proc-macro trait introduces a special derive
macro called Recursive
that can be used to auto-generate a recursive implementation of most traits you'll be writing.
Using Recursive
with structs
Let's look at the following trait:
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
}
}
Here, the count
function should sum up all i32
values in a piece of data. Suppose we want to implement it for the following struct:
struct Foo {
bar: i32,
zee: Vec<i32>
}
First of all, we have to derive Recursive
for our type (and import the macro):
use derive_recursive::Recursive;
// ...
#[derive(Recursive)]
struct Foo {
bar: i32,
zee: Vec<i32>
}
But this alone won't give us anything. In order to actually generate some code, we need to tell the library what traits to implement and how to implement their functions. We do this using the recursive
helper macro:
#[derive(Recursive)]
#[recursive(
impl Count for Self {
fn count(&self) -> i32 {
aggregate = +
}
}
)]
struct Foo {
bar: i32,
zee: Vec<i32>
}
That's a lot of code, but, as you'll see, it's all really simple. First, we specify which trait we want to implement through a standard impl block. Note that it really is a standard impl block, as it also accepts generics the same way. That is, should the trait or the self-type have any generics, you'd write them as though you were actually implementing the trait. The only difference from a standard impl block is the forced usage of Self
, rather than a path. Self
here is only a substitute for the type's name, not the generics. Any generics must actually be given. When we're done specifying the trait, we specify what functions we want to implement. We have to write the full signatures of all required (and maybe optional) functions, as derive_recursive
doesn't know anything about the traits it implements. The signature is written exactly as if it were in an actual impl block. Then, where normally the function's code would be, we put implementation parameters.
Implementation parameters are how we specify what exactly should be done with the function. Here, we used aggregate
to tell Recursive
to merge the results of function calls on Foo
's fields with the +
operator. The aggregate
parameter also accepts *
, &
, |
, &&
, ||
as operators, _
as a special unit
aggregate, that only returns the result of the last recursive call, and a function path for custom aggregation. This works as expected with associated functions (field types). The aggregate
parameter can also become {}
denoting a constructor. The call results are then put together into a Self
-typed return value. If no aggregate
parameter is given, only the first field in the struct is processed. It's also worth noting, that any parameters of the recursive function are passed on to the next calls.
Ultimately, it all expands into the following
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) }
}
}
Should you want to implement another trait for Foo
this way, you need a new recursive
helper macro. The below example shows a few concepts mentioned above:
#[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>
}
This expands into
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),
}
}
}
Fields affected by the implemented function can also be filtered with the marker
attribute:
#[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>
}
Then we can mark the right fields with #[recursive(<marker>)]
:
struct Foo {
#[recursive(only_this)]
a: String,
b: u32,
#[recursive(only_this)]
c: Vec<&'static str>
}
This expands into the following:
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)
}
}
}
If aggregate was not present, the first field found with the right marker would be affected. You can add multiple markers to the same field, of course. It's worth mentioning that if the aggregate is {}
, the marker
attribute is invalid. Construction must affect all fields.
Another useful attribute is the wrap
attribute. This can be given two different values: Result
and Option
. If the attribute is present, all recursive function calls are followed by a ?
and the final returned value is wrapped into the respective variant (Ok
or Some
).
with enums
recursive_derives
can also work with enums. All attributes mentioned above still apply and marker
along with aggregate
modify how variants are processed. For interactions (aggregation and filtering) with the enum's variants, we use variant_aggregate
and variant_marker
. variant_aggregate
is only allowed with non-constructive (attribute
is not {}
) associated functions, as there is no aggregation possible when the function gets a receiver. The attribute decides how results produced by recursively applying the functions on the variants are aggregated. It accepts any values the aggregate
attribute accepts, EXCEPT {}
. variant_marker
can be used to filter affected variants. It's only allowed on associated functions and REQUIRED on associated constructive functions (aggregate
is {}
). It works exactly the same way the marker
attribute does, except this time, we apply attributes to the variants themselves.
The below example presents deriving traits such as Default
and Clone
from the standard library and our own Size
trait, determining the memory size of the implementor:
#[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>
}
}
This expands into
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() },
)
}
}
Additional attributes
init
and variant_init
You can use the init
attribute (accepts an expression) to add a special init expression that will be aggregated with subsequent recursive calls. The init expression is always the first executed one. You can achieve a similar result when aggregating enum variants using variant_init
.
override_marker
The override_marker
attribute can be used to add a custom function to call in place of a recursive call for a specific variant/field. The same attribute can be used for both. Variants and fields should be marked, like with other marker attributes, with one change:
#[recursive(marker_name = func_here)]
struct Foo;
The right hand side of the statement should be an expression returning a function (which means it also allows closures).
Disclaimer
Note, that this library quite fresh, has not been very tested and is limited in some ways (most notably, it has very limited support for unit structs and variants). However, it should be good enough for most use cases. Should you discover a bug or stumble upon an idea on how to improve the library (including things you need, but the library does not provide), feel free to open an issue (and maybe make the change yourself and add a PR, if you have the time). Note that I won't be working on the library a lot, since I have other, more important projects.
Dependencies
~210–650KB
~15K SLoC