Crates.io | rules_derive |
lib.rs | rules_derive |
version | 0.2.0 |
created_at | 2025-07-29 01:30:40.268811+00 |
updated_at | 2025-09-24 16:56:18.368137+00 |
description | simple and fast derive macros using macro_rules |
homepage | |
repository | https://github.com/MatX-inc/rules_derive |
max_upload_size | |
id | 1771778 |
size | 79,783 |
macro_rules
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:
impl
headers.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.