This library allows you to define custom deriving instances using
macro_rules! macros rather than proc-macros. This is often much simpler.
Define a deriving macro with macro_rules!():
macro_rules! MyTrait {
(/* see `rules_derive` for definition of signature */) => {
// Generate impl
impl $($generics_bindings)* MyTrait for $ty where $($generics_where)* {
// implementation here
}
}
}Then use it under the rules_derive attribute:
#[rules_derive(MyTrait)]
struct MyType { x: u32, y: String }The macro definition can be in the same crate or file as its use.
See full examples in the examples directory.
See the announcement blog post for a tutorial.
The rules_derive macro parses any enum/struct definition into a simpler-to-parse format, which it then
passes to your macro. The primary transformations it does are:
- Convert all enum/struct syntaxes and named-field/unnamed-field/unit syntaxes into a uniform sum-of-products syntax.
- Convert any generic parameters in the typical ways needed for
implheaders.
The motivation for these transformations is given in the announcement blog post. Here is an example of the effect of this transformation:
// Rust type definition:
#[rustfmt::skip]
pub enum Foo<T: Clone = u8> where u8: Into<T> {
A { x: T },
B,
C(u8),
}
// rules_derive-transformed type definition:
((#[rustfmt::skip]))
pub enum Foo((Foo<T>) (<T: Clone>) where (u8: Into<T>,))
{
A(named Foo::A) { field__x @ x : T, }
B(unit Foo::B) {}
C(unnamed Foo::C) { field__0 @ 0 : u8, }
}This transformed type definition is then passed to your macro. You can see the macro_rules! header
that accepts this transformed type definition on the [rules_derive] documentation.
The table below compares rules_derive with other approaches for creating derive macros in Rust:
| Feature | syn/quote | rules_derive | macro_rules_attribute/RFC 3698 | derive-deftly | synstructure | crabtime |
|---|---|---|---|---|---|---|
Quality: Generic type (Foo<T>) support |
✅ | ✅ | ❌ | ✅ | ✅ | ❌ |
| Quality: Good error attribution | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ |
| Quality: Attribute parsing | ✅ | 🚧 | ❌ | ✅ | ✅ | ❌ |
| Dependency size | ~40k LoC | <1k LoC | <1k LoC | 60k+ LoC | 60k+ LoC | Huge (rustc) |
| Ergonomics: Same-crate derivers | ❌ | ✅ | ✅ | ✅ | ❌ | ✅ |
| Ergonomics: Uniform type syntax | ❌ | ✅ | ❌ | ✅ | ✅ | ❌ |
| Generality: Opinionated approach | No | No | No | Yes (custom template language) | Yes (fold/foreach abstractions) | No |
Legend: ✅ Yes, ❌ No, 🚧 Under construction
We aim for rules_derive to be a viable candidate for implementing derivers for core ecosystem libraries such as zerocopy, strum, serde. All such libraries today use the standard syn/quote approach rather than any of the alternatives, and we believe this is because syn/quote is the smallest high-quality approach: smallest means the smallest transitive dependency size; and high-quality means that derivers have generic type support, good error attribution, and (perhaps) attribute parsing. As we see it, rules_derive is the first approach that meets the syn/quote quality bar while also being a far smaller dependency.
The ecosystem advantage that might be possible by a smaller high-quality approach is that core ecosystem libraries might get to a point where they no longer need to carefully feature-gate all their use of derive macros.
syn/quote is by far the standard approach. rules_derive is far smaller of a dependency and is typically also more concise, but we aim to hit the same quality bar as syn/quote derivers for the vast majority of use cases. For some long tail use cases, e.g. ones that run complex algorithms at macro time, syn/quote will likely remain more flexible long-term.
synstructure builds on syn/quote but imposes a fold/foreach-based programming model that from our perspective is a little too opinionated. We share with synstructure the idea of expressing all types as a sum-of-products (or list of variants).
derive-deftly shares many core ideas with rules_derive: macro_rules-based implementation plus a single-time proc-macro that parses type definitions into a uniform syntax. The biggest difference is that derive-deftly is based on a new template language for derivers that is similar to macro_rules but but slightly different and with richer templating features such as loops and ifs. In contrast, rules_derive stays with Rust's macro_rules language, and when we want to add new functionality we add new Rust macros. Ultimately this becomes a tradeoff between features and simplicity: rules_derive is 800 lines of code and has zero dependencies, whereas derive-deftly is 8k lines of code and has transitive dependencies of ~60k lines of code.
macro_rules_attribute is similarly lightweight to Rust, but does not provide any support for parsing generic types, transforming type definitions to a uniform syntax, or offering good error attribution. This seems to make it a reasonable fit for simple examples, but not for producing deriving macros of similar quality to what you can do with syn/quote.
RFC 3698 would bake the approach of macro_rules_attribute into the Rust language, but again does not tackle the "awkward squad" of generic types, uniform type definition syntax, error attribution.
crabtime offers a very nice custom syntax for defining macros---substantially nicer than the macro_rules! syntax that Rust ships with---but its implementation is heavyweight: it is a proc macro that internally invokes the Rust compiler as a subprocess. This is far beyond the normal hermeticity expectations of a proc macro, and seems like it may pose challenges for other build environments such as bazel which enforce strong isolation of build actions.