Skip to content

Commit 192c533

Browse files
authored
Merge pull request #3698 from joshtriplett/declarative-derive-macros
Declarative `macro_rules!` derive macros - `macro_derive`
2 parents 9ef4dc5 + d572502 commit 192c533

File tree

1 file changed

+303
-0
lines changed

1 file changed

+303
-0
lines changed
Lines changed: 303 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,303 @@
1+
- Feature Name: `macro_derive`
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#143549](https://github.com/rust-lang/rust/issues/143549)
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+
The [`macro_rules_attribute`](https://crates.io/crates/macro_rules_attribute)
26+
crate defines proc macros that allow invoking declarative macros as derives,
27+
demonstrating a demand for this. This feature would allow defining such derives
28+
without requiring proc macros at all, and would support the same invocation
29+
syntax as a proc macro.
30+
31+
The derive feature of the crate has [various uses in the
32+
ecosystem](https://github.com/search?q=macro_rules_attribute%3A%3Aderive&type=code).
33+
34+
`derive` macros have a standard syntax that Rust users have come to expect for
35+
defining traits; this motivates providing users a way to invoke that mechanism
36+
for declarative macros. An attribute or a `macro_name!` invocation could serve
37+
the same purpose, but that would be less evocative than `derive(Trait)` for
38+
the purposes of making the purpose of the macro clear, and would additionally
39+
give the macro more power to rewrite the underlying definition. Derive macros
40+
simplify tools like rust-analyzer, which can know that a derive macro will
41+
never change the underlying item definition.
42+
43+
# Guide-level explanation
44+
[guide-level-explanation]: #guide-level-explanation
45+
46+
You can define a macro to implement `derive(MyTrait)` by defining a
47+
`macro_rules!` macro with one or more `derive()` rules. Such a macro can create
48+
new items based on a struct, enum, or union. Note that the macro can only
49+
append new items; it cannot modify the item it was applied to.
50+
51+
For example:
52+
53+
```rust
54+
trait Answer { fn answer(&self) -> u32; }
55+
56+
macro_rules! Answer {
57+
// Simplified for this example
58+
derive() (struct $n:ident $_:tt) => {
59+
impl Answer for $n {
60+
fn answer(&self) -> u32 { 42 }
61+
}
62+
};
63+
}
64+
65+
#[derive(Answer)]
66+
struct Struct;
67+
68+
fn main() {
69+
let s = Struct;
70+
assert_eq!(42, s.answer());
71+
}
72+
```
73+
74+
Derive macros defined using `macro_rules!` follow the same scoping rules as
75+
any other macro, and may be invoked by any path that resolves to them.
76+
77+
A derive macro may share the same path as a trait of the same name. For
78+
instance, the name `mycrate::MyTrait` can refer to both the `MyTrait` trait and
79+
the macro for `derive(MyTrait)`. This is consistent with existing derive
80+
macros.
81+
82+
If a derive macro emits a trait impl for the type, it may want to add the
83+
[`#[automatically_derived]`](https://doc.rust-lang.org/reference/attributes/derive.html#the-automatically_derived-attribute)
84+
attribute, for the benefit of diagnostics.
85+
86+
If a derive macro mistakenly emits the token stream it was applied to
87+
(resulting in a duplicate item definition), the error the compiler emits for
88+
the duplicate item should hint to the user that the macro was defined
89+
incorrectly, and remind the user that derive macros only append new items.
90+
91+
A `derive()` rule can be marked as `unsafe`:
92+
`unsafe derive() (...) => { ... }`.
93+
Invoking such a derive using a rule marked as `unsafe`
94+
requires `unsafe` derive syntax:
95+
`#[derive(unsafe(DangerousDeriveMacro))]`
96+
97+
Invoking an unsafe derive rule without the unsafe derive syntax will produce a
98+
compiler error. Using the unsafe derive syntax without an unsafe derive will
99+
trigger an "unused unsafe" lint. (RFC 3715 defines the equivalent mechanism for
100+
proc macro derives.)
101+
102+
# Reference-level explanation
103+
[reference-level-explanation]: #reference-level-explanation
104+
105+
The grammar for macros is extended as follows:
106+
107+
> _MacroRule_ :\
108+
> &nbsp;&nbsp; ( `unsafe`<sup>?</sup> `derive` `(` `)` )<sup>?</sup> _MacroMatcher_ `=>` _MacroTranscriber_
109+
110+
The _MacroMatcher_ matches the entire construct the attribute was
111+
applied to, receiving precisely what a proc-macro-based attribute
112+
would in the same place.
113+
114+
(The empty parentheses after `derive` reserve future syntax space
115+
for derives accepting arguments, at which time they'll be replaced
116+
by a second _MacroMatcher_ that matches the arguments.)
117+
118+
A derive invocation that uses an `unsafe derive` rule will produce
119+
an error if invoked without using the `unsafe` derive syntax. A
120+
derive invocation that uses an `derive` rule (without `unsafe`)
121+
will trigger the "unused unsafe" lint if invoked using the `unsafe`
122+
derive syntax. A single derive macro may have both `derive` and
123+
`unsafe derive` rules, such as if only some invocations are unsafe.
124+
125+
This grammar addition is backwards compatible: previously, a _MacroRule_ could
126+
only start with `(`, `[`, or `{`, so the parser can easily distinguish rules
127+
that start with `derive` or `unsafe`.
128+
129+
Adding `derive` rules to an existing macro is a semver-compatible change,
130+
though in practice, it will likely be uncommon.
131+
132+
If a user invokes a macro as a derive and that macro does not have any `derive`
133+
rules, the compiler should give a clear error stating that the macro is not
134+
usable as a derive because it does not have any `derive` rules.
135+
136+
# Drawbacks
137+
[drawbacks]: #drawbacks
138+
139+
This feature will not be sufficient for *all* uses of proc macros in the
140+
ecosystem, and its existence may create social pressure for crate maintainers
141+
to switch even if the result is harder to maintain. We can and should attempt
142+
to avert and such pressure, such as by providing a post with guidance that
143+
crate maintainers can link to when responding to such requests.
144+
145+
Before stabilizing this feature, we should receive feedback from crate
146+
maintainers, and potentially make further improvements to `macro_rules` to make
147+
it easier to use for their use cases. This feature will provide motivation to
148+
evaluate many new use cases that previously weren't written using
149+
`macro_rules`, and we should consider quality-of-life improvements to better
150+
support those use cases.
151+
152+
# Rationale and alternatives
153+
[rationale-and-alternatives]: #rationale-and-alternatives
154+
155+
Adding this feature will allow many crates in the ecosystem to drop their proc
156+
macro crates and corresponding dependencies, and decrease their build times.
157+
158+
This will also give derive macros access to the `$crate` mechanism to refer to
159+
the defining crate, which is simpler than mechanisms currently used in proc
160+
macros to achieve the same goal.
161+
162+
Macros defined this way can more easily support caching, as they cannot depend
163+
on arbitrary unspecified inputs.
164+
165+
Crates could instead define `macro_rules!` macros and encourage users to invoke
166+
them using existing syntax like `macroname! { ... }`, rather than using
167+
derives. This would provide the same functionality, but would not support the
168+
same syntax people are accustomed to, and could not maintain semver
169+
compatibility with an existing proc-macro-based derive. In addition, this would
170+
not preserve the property derive macros normally have that they cannot change
171+
the item they are applied to.
172+
173+
A mechanism to define attribute macros would let people write attributes like
174+
`#[derive_mytrait]`, but that would not provide compatibility with existing
175+
derive syntax.
176+
177+
We could allow `macro_rules!` derive macros to emit a replacement token stream.
178+
That would be inconsistent with the restriction preventing proc macros from
179+
doing the same, but it would give macros more capabilities, and simplify some
180+
use cases. Notably, that would make it easy for derive macros to re-emit a
181+
structure with another `derive` attached to it.
182+
183+
We could allow directly invoking a `macro_rules!` derive macro as a
184+
function-like macro. This has the potential for confusion, given the
185+
append-only nature of derive macros versus the behavior of normal function-like
186+
macros. It might potentially be useful for code reuse, however.
187+
188+
## Syntax alternatives
189+
190+
Rather than using `derive()` rules, we could have `macro_rules!` macros use a
191+
`#[macro_derive]` attribute, similar to the `#[proc_macro_derive]` attribute
192+
used for proc macros.
193+
194+
However, this would be inconsistent with `attr()` rules as defined in RFC 3697.
195+
This would also make it harder to add parameterized derives in the future (e.g.
196+
`derive(MyTrait(params))`).
197+
198+
# Prior art
199+
[prior-art]: #prior-art
200+
201+
We have had proc-macro-based derive macros for a long time, and the ecosystem
202+
makes extensive use of them.
203+
204+
The [`macro_rules_attribute`](https://crates.io/crates/macro_rules_attribute)
205+
crate defines proc macros that allow invoking declarative macros as derives,
206+
demonstrating a demand for this. This feature would allow defining such derives
207+
without requiring proc macros at all, and would support the same invocation
208+
syntax as a proc macro.
209+
210+
The derive feature of the crate has [various uses in the
211+
ecosystem](https://github.com/search?q=macro_rules_attribute%3A%3Aderive&type=code).
212+
213+
# Unresolved questions
214+
[unresolved-questions]: #unresolved-questions
215+
216+
Before stabilizing this feature, we should ensure there's a mechanism macros
217+
can use to ensure that an error when producing an impl does not result in a
218+
cascade of additional errors caused by a missing impl. This may take the form
219+
of a fallback impl, for instance.
220+
221+
Before stabilizing this feature, we should make sure it doesn't produce wildly
222+
worse error messages in common cases.
223+
224+
Before stabilizing this feature, we should receive feedback from crate
225+
maintainers, and potentially make further improvements to `macro_rules` to make
226+
it easier to use for their use cases. This feature will provide motivation to
227+
evaluate many new use cases that previously weren't written using
228+
`macro_rules`, and we should consider quality-of-life improvements to better
229+
support those use cases.
230+
231+
Before stabilizing this feature, we should have clear public guidance
232+
recommending against pressuring crate maintainers to adopt this feature
233+
rapidly, and encourage crate maintainers to link to that guidance if such
234+
requests arise.
235+
236+
# Future possibilities
237+
[future-possibilities]: #future-possibilities
238+
239+
We should provide a way for derive macros to invoke other derive macros. The
240+
`macro_rules_attribute` crate includes a `derive_alias` mechanism, which we
241+
could trivially implement given a means of invoking another derive macro.
242+
243+
We should provide a means to perform a `derive` on a struct without being
244+
directly attached to that struct. (This would also potentially require
245+
something like a compile-time reflection mechanism.)
246+
247+
We could support passing parameters to derive macros (e.g.
248+
`#[derive(Trait(params), OtherTrait(other, params))]`). This may benefit from
249+
having `derive(...)` rules inside the `macro_rules!` macro declaration, similar
250+
to the `attr(...)` rules proposed in RFC 3697.
251+
252+
In the future, if we support something like `const Trait` or similar trait
253+
modifiers, we'll want to support `derive(const Trait)`, and define how a
254+
`macro_rules!` derive handles that.
255+
256+
We should provide a way for `macro_rules!` macros to provide better error
257+
reporting, with spans, rather than just pointing to the macro.
258+
259+
We may want to support error recovery, so that a derive can produce an error
260+
but still provide enough for the remainder of the compilation to proceed far
261+
enough to usefully report further errors.
262+
263+
As people test this feature and run into limitations of `macro_rules!` parsing,
264+
we should consider additional features to make this easier to use for various
265+
use cases.
266+
267+
We could provide a macro matcher to match an entire struct field, along with
268+
syntax (based on macro metavariable expressions) to extract the field name or
269+
type (e.g. `${f.name}`). This would simplify many common cases by leveraging
270+
the compiler's own parser.
271+
272+
We could do the same for various other high-level constructs.
273+
274+
We may want to provide simple helpers for generating/propagating `where`
275+
bounds, which would otherwise be complex to do in a `macro_rules!` macro.
276+
277+
We may want to add a lint for macro names, encouraging macros with derive rules
278+
to use `CamelCase` names, and encouraging macros without derive rules to use
279+
`snake_case` names.
280+
281+
## Helper attribute namespacing and hygiene
282+
283+
We should provide a way for derive macros to define helper attributes ([inert
284+
attributes](https://doc.rust-lang.org/reference/attributes.html#active-and-inert-attributes)
285+
that exist for the derive macro to parse and act upon). Such attributes are
286+
supported by proc macro derives; however, such attributes have no namespacing,
287+
and thus currently represent compatibility hazards because they can conflict.
288+
We should provide a namespaced, hygienic mechanism for defining and using
289+
helper attributes.
290+
291+
For instance, could we have `pub macro_helper_attr! skip` in the standard
292+
library, namespaced under `core::derives` or similar? Could we let macros parse
293+
that in a way that matches it in a namespaced fashion, so that:
294+
- If you write `#[core::derives::skip]`, the macro matches it
295+
- If you `use core::derives::skip;` and `write #[skip]`, the macro matches it
296+
- If you `use elsewhere::skip` (or no import at all) and write `#[skip]`, the
297+
macro *doesn't* match it.
298+
299+
We already have *some* interaction between macros and name resolution, in order
300+
to have namespaced `macro_rules!` macros. Would something like this be feasible?
301+
302+
(We would still need to specify the exact mechanism by which macros match these
303+
helper attributes.)

0 commit comments

Comments
 (0)