Skip to content

Commit a3cd084

Browse files
committed
Declarative macro_rules! derive macros
1 parent ffb2c46 commit a3cd084

File tree

1 file changed

+110
-0
lines changed

1 file changed

+110
-0
lines changed
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
- Feature Name: `declarative_derive_macros`
2+
- Start Date: 2024-09-20
3+
- RFC PR: [rust-lang/rfcs#3698](https://github.com/rust-lang/rfcs/pull/3698)
4+
- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000)
5+
6+
# Summary
7+
[summary]: #summary
8+
9+
Support implementing `derive(Trait)` via a `macro_rules!` macro.
10+
11+
# Motivation
12+
[motivation]: #motivation
13+
14+
Many crates support deriving their traits with `derive(Trait)`. Today, this
15+
requires defining proc macros, in a separate crate, typically with several
16+
additional dependencies adding substantial compilation time, and typically
17+
guarded by a feature that users need to remember to enable.
18+
19+
However, many common cases of derives don't require any more power than an
20+
ordinary `macro_rules!` macro. Supporting these common cases would allow many
21+
crates to avoid defining proc macros, reduce dependencies and compilation time,
22+
and provide these macros unconditionally without requiring the user to enable a
23+
feature.
24+
25+
# Guide-level explanation
26+
[guide-level-explanation]: #guide-level-explanation
27+
28+
You can define a macro to implement `derive(MyTrait)` by defining a
29+
`macro_rules!` macro with the `#[macro_derive]` attribute. Such a macro can
30+
create new items based on a struct, enum, or union. Note that the macro can
31+
only append new items; it cannot modify the item it was applied to.
32+
33+
For instance, the following macro will ignore the item it is attached to, and
34+
append a function `answer()`:
35+
36+
```rust
37+
#[macro_derive]
38+
macro_rules! AnswerFn {
39+
($_:tt) => { fn answer() -> u32 { 42 } };
40+
}
41+
42+
#[derive(AnswerFn)]
43+
struct Struct;
44+
45+
fn main() {
46+
assert_eq!(42, answer());
47+
}
48+
```
49+
50+
Derive macros defined using `macro_rules!` follow the same scoping rules as
51+
any other macro, and may be invoked by any path that resolves to them.
52+
53+
A derive macro may share the same path as a trait of the same name. For
54+
instance, the name `mycrate::MyTrait` can refer to both the `MyTrait` trait and
55+
the macro for `derive(MyTrait)`. This is consistent with existing derive
56+
macros.
57+
58+
A derive macro may also define *helper attributes*. These attributes are
59+
[inert](https://doc.rust-lang.org/reference/attributes.html#active-and-inert-attributes),
60+
and their only purpose is to be fed into the derive macro that defined them.
61+
That said, they can be seen by all macros.
62+
63+
To define helper attributes, put an attributes key in the `macro_derive`
64+
attribute, with a comma-separated list of identifiers for helper attributes:
65+
`#[macro_derive(attributes(helper))]`. The derive macro can process the
66+
`#[helper]` attribute, along with any arguments to it, as part of the item the
67+
derive macro was applied to.
68+
69+
If a derive macro mistakenly emits the token stream it was applied to
70+
(resulting in a duplicate item definition), the error the compiler emits for
71+
the duplicate item should hint to the user that the macro was defined
72+
incorrectly, and remind the user that derive macros only append new items.
73+
74+
# Rationale and alternatives
75+
[rationale-and-alternatives]: #rationale-and-alternatives
76+
77+
Adding this feature will allow many crates in the ecosystem to drop their proc
78+
macro crates and corresponding dependencies, and decrease their build times.
79+
80+
Crates could instead define `macro_rules!` macros and encourage users to invoke
81+
them using existing syntax like `macroname! { ... }`, rather than using
82+
derives. This would provide the same functionality, but would not support the
83+
same syntax people are accustomed to, and could not maintain semver
84+
compatibility with an existing proc-macro-based derive. In addition, this would
85+
not preserve the property derive macros normally have that they cannot change
86+
the item they are applied to.
87+
88+
A mechanism to define attribute macros would let people write attributes like
89+
`#[derive_mytrait]`, but that would not provide compatibility with existing
90+
derive syntax.
91+
92+
We could allow `macro_rules!` derive macros to emit a replacement token stream;
93+
however, that would be inconsistent with the restriction preventing proc macros
94+
from doing the same.
95+
96+
# Prior art
97+
[prior-art]: #prior-art
98+
99+
We have had proc-macro-based derive macros for a long time, and the ecosystem
100+
makes extensive use of them.
101+
102+
The [`macro_rules_attribute`](https://crates.io/crates/macro_rules_attribute)
103+
crate defines proc macros that allow invoking declarative macros as derives,
104+
demonstrating a demand for this. This feature would allow defining such derives
105+
without requiring proc macros at all, and would support the same invocation
106+
syntax as a proc macro.
107+
108+
The `macro_derive` attribute and its `attributes` syntax are based on the
109+
[existing `proc_macro_derive` attribute for proc
110+
macros](https://doc.rust-lang.org/reference/procedural-macros.html#derive-macros).

0 commit comments

Comments
 (0)