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:
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:
First of all, we have to derive Recursive for our type (and import the macro):
use Recursive;
// ...
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:
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
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:
This expands into
Fields affected by the implemented function can also be filtered with the marker attribute:
Then we can mark the right fields with #[recursive(<marker>)]:
This expands into the following:
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:
This expands into
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:
;
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.