|
| 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 | +> ( `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