Skip to content

Commit ee1e93c

Browse files
committed
#[derive(Default)] on enums with #[default]
1 parent 5956896 commit ee1e93c

File tree

1 file changed

+344
-0
lines changed

1 file changed

+344
-0
lines changed

text/0000-derive-enum-default.md

+344
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,344 @@
1+
- Feature Name: `derive_enum_default`
2+
- Start Date: 2021-04-07
3+
- RFC PR: [rust-lang/rfcs#3107](https://github.com/rust-lang/rfcs/pull/3107)
4+
- Rust Issue: TODO
5+
6+
# Summary
7+
[summary]: #summary
8+
9+
An attribute `#[default]`, usable on `enum` unit variants, is also introduced, thereby allowing some
10+
enums to work with `#[derive(Default)]`.
11+
12+
```rust
13+
#[derive(Default)]
14+
enum Padding {
15+
Space,
16+
Zero,
17+
#[default]
18+
None,
19+
}
20+
21+
assert_eq!(Padding::default(), Padding::None);
22+
```
23+
24+
The `#[default]` and `#[non_exhaustive]` attributes may not be used on the same variant.
25+
26+
# Motivation
27+
[motivation]: #motivation
28+
29+
## `#[derive(Default)]` in more cases
30+
31+
Currently, `#[derive(Default)]` is not usable on `enum`s. To partially rectify this situation, a
32+
`#[default]` attribute is introduced that can be attached to unit variants. This allows you to use
33+
`#[derive(Default)]` on enums wherefore you can now write:
34+
35+
```rust
36+
#[derive(Default)]
37+
enum Padding {
38+
Space,
39+
Zero,
40+
#[default]
41+
None,
42+
}
43+
```
44+
45+
# Guide-level explanation
46+
[guide-level-explanation]: #guide-level-explanation
47+
48+
The ability to add default values to fields of `enum` variants does not mean that you can suddenly
49+
`#[derive(Default)]` on the enum. A Rust compiler will still have no idea which variant you intended
50+
as the default. This RFC adds the ability to mark one unit variant with `#[default]`:
51+
52+
```rust
53+
#[derive(Default)]
54+
enum Ingredient {
55+
Tomato,
56+
Onion,
57+
#[default]
58+
Lettuce,
59+
}
60+
```
61+
62+
Now the compiler knows that `Ingredient::Lettuce` should be considered the default and will
63+
accordingly generate an appropriate implementation:
64+
65+
```rust
66+
impl Default for Ingredient {
67+
fn default() -> Self {
68+
Ingredient::Lettuce
69+
}
70+
}
71+
```
72+
73+
Note that after any `cfg`-stripping has occurred, it is an error to have `#[default]` specified on
74+
zero or multiple variants.
75+
76+
As fields may be added to `#[non_exhaustive]` variants that necessitate additional bounds, it is not
77+
permitted to place `#[default]` and `#[non_exhaustive]` on the same variant.
78+
79+
# Reference-level explanation
80+
[reference-level-explanation]: #reference-level-explanation
81+
82+
## `#[default]` on `enum`s
83+
84+
An attribute `#[default]` is provided the compiler and may be legally placed solely on one
85+
exhaustive `enum` unit variants. The attribute has no semantics on its own. Placing the attribute on
86+
anything else will result in a compilation error. Furthermore, if the attribute occurs on zero or
87+
multiple variants of the same `enum` data-type after `cfg`-stripping and macro expansion is done,
88+
this will also result in a compilation error.
89+
90+
## `#[derive(Default)]`
91+
92+
Placing `#[derive(Default)]` on an `enum` named `$e` is permissible if and only if that enum has
93+
some variant `$v` with `#[default]` on it. In that event, the compiler shall generate the following:
94+
implementation of `Default` where the function `default` is defined as:
95+
96+
```rust
97+
impl ::core::default::Default for $e {
98+
fn default() -> Self {
99+
$e::$v
100+
}
101+
}
102+
```
103+
104+
### Generated bounds
105+
106+
As exhaustive unit variants have no inner types, no bounds shall be generated on the derived
107+
implementation. For example,
108+
109+
```rust
110+
#[derive(Default)]
111+
enum Option<T> {
112+
#[default]
113+
None,
114+
Some(T),
115+
}
116+
```
117+
118+
would generate:
119+
120+
```rust
121+
impl<T> Default for Option<T> {
122+
fn default() -> Self {
123+
Option::None
124+
}
125+
}
126+
```
127+
128+
## Interaction with `#[non_exhaustive]`
129+
130+
The Rust compiler shall not permit `#[default]` and `#[non_exhaustive]` to be present on the same
131+
variant. Non-default variants may be `#[non_exhaustive]`, as can the `enum` itself.
132+
133+
# Drawbacks
134+
[drawbacks]: #drawbacks
135+
136+
The usual drawback of increasing the complexity of the language applies. However, the degree to
137+
which complexity is increased is not substantial. One notable change is the addition of an attribute
138+
for a built-in `#[derive]`, which has no precedent.
139+
140+
# Rationale
141+
[rationale]: #rationale
142+
143+
The inability to derive `Default` on `enum`s has been noted on a number of occasions, with a common
144+
suggestion being to add a `#[default]` attribute (or similar) as this RFC proposes.
145+
146+
- [IRLO] [Request: derive enum's default][rationale-1]
147+
- [IRLO] [Deriving `Error` (comment)][rationale-2]
148+
- [URLO] [Crate for macro for default enum variant][rationale-3]
149+
- [URLO] [`#[derive(Default)]` for enum, [not] only struct][rationale-4]
150+
151+
[rationale-1]: https://internals.rust-lang.org/t/request-derive-enums-default/10576?u=jhpratt
152+
[rationale-2]: https://internals.rust-lang.org/t/deriving-error/11894/10?u=jhpratt
153+
[rationale-3]: https://users.rust-lang.org/t/crate-for-macro-for-default-enum-variant/44032?u=jhpratt
154+
[rationale-4]: https://users.rust-lang.org/t/derive-default-for-enum-non-only-struct/44046?u=jhpratt
155+
156+
In the interest of forwards compatibility, this RFC is limited to only exhaustive unit variants.
157+
Were this not the case, adding a field to a `#[non_exhaustive]` variant could lead to more stringent
158+
bounds being generated, which is a breaking change. For example,
159+
160+
A definition of
161+
162+
```rust
163+
#[derive(Default)]
164+
enum Foo<T> {
165+
#[default]
166+
#[non_exhaustive]
167+
Alpha,
168+
Beta(T),
169+
}
170+
```
171+
172+
would not have any required bounds on the generated code. If this were changed to
173+
174+
```rust
175+
#[derive(Default)]
176+
enum Foo<T> {
177+
#[default]
178+
#[non_exhaustive]
179+
Alpha(T),
180+
Beta(T),
181+
}
182+
```
183+
184+
then any code where `T: !Default` would now fail to compile, on the assumption that the generated
185+
code for the latter has the `T: Default` bound (nb: not part of this RFC).
186+
187+
# Alternatives
188+
[alternatives]: #alternatives
189+
190+
One alternative is to permit the user to declare the default variant in the derive itself, such as
191+
`#[derive(Default(VariantName))]`. This has the disadvantage that the variant name is present in
192+
multiple locations in the declaration, increasing the likelihood of a typo (and thus an error).
193+
194+
Another alternative is assigning the first variant to be default when `#[derive(Default)]` is
195+
present. This may prevent a `#[derive(PartialOrd)]` on some `enum`s where order is important (unless
196+
the user were to explicitly assign the discriminant).
197+
198+
# Prior art
199+
[prior-art]: #prior-art
200+
201+
## Procedural macros
202+
203+
There are a number of crates which to varying degrees afford macros for default field values and
204+
associated facilities.
205+
206+
### `#[derive(Derivative)]`
207+
208+
[`derivative`]: https://crates.io/crates/derivative
209+
210+
The crate [`derivative`] provides the `#[derivative(Default)]` attribute. With it, you may write:
211+
212+
```rust
213+
#[derive(Derivative)]
214+
#[derivative(Default)]
215+
enum Foo {
216+
#[derivative(Default)]
217+
Bar,
218+
Baz,
219+
}
220+
```
221+
222+
Contrast this with the equivalent in the style of this RFC:
223+
224+
```rust
225+
#[derive(Default)]
226+
enum Foo {
227+
#[default]
228+
Bar,
229+
Baz,
230+
}
231+
```
232+
233+
Like in this RFC, `derivative` allows you to derive `Default` for `enum`s. The syntax used in the
234+
macro is `#[derivative(Default)]` whereas the RFC provides the more ergonomic and direct notation
235+
`#[default]` in this RFC.
236+
237+
### `#[derive(SmartDefault)]`
238+
239+
[`smart-default`]: https://crates.io/crates/smart-default
240+
241+
The [`smart-default`] provides `#[derive(SmartDefault)]` custom derive macro. It functions similarly
242+
to `derivative` but is specialized for the `Default` trait. With it, you can write:
243+
244+
```rust
245+
#[derive(SmartDefault)]
246+
enum Foo {
247+
#[default]
248+
Bar,
249+
Baz,
250+
}
251+
```
252+
253+
- The same syntax `#[default]` is used both by `smart-default` and by this RFC. While it may seem
254+
that this RFC was inspired by `smart-default`, this is not the case. Rather, this notation has
255+
been independently thought of on multiple occasions. That suggests that the notation is intuitive
256+
and a solid design choice.
257+
258+
- There is no trait `SmartDefault` even though it is being derived. This works because
259+
`#[proc_macro_derive(SmartDefault)]` is in fact not tied to any trait. That `#[derive(Serialize)]`
260+
refers to the same trait as the name of the macro is from the perspective of the language's static
261+
semantics entirely coincidental.
262+
263+
However, for users who aren't aware of this, it may seem strange that `SmartDefault` should derive
264+
for the `Default` trait.
265+
266+
# Unresolved questions
267+
[unresolved-questions]: #unresolved-questions
268+
269+
- None so far.
270+
271+
# Future possibilities
272+
[future-possibilities]: #future-possibilities
273+
274+
## Non-unit variants
275+
276+
One significant future possibility is to have `#[default]` permitted on non-unit variants. This was
277+
originally proposed as part of this RFC but has been postponed due to disagreement over what the
278+
generated bounds should be. This is largely due to the fact that [`#[derive(Default)]` on `struct`s
279+
may generate incorrect bounds](https://github.com/rust-lang/rust/issues/26925).
280+
281+
## Overriding default fields
282+
283+
The `#[default]` attribute could be extended to override otherwise derived default values, such as
284+
285+
```rust
286+
#[derive(Default)]
287+
struct Foo {
288+
alpha: u8,
289+
#[default = 1]
290+
beta: u8,
291+
}
292+
```
293+
294+
which would result in
295+
296+
```rust
297+
impl Default for Foo {
298+
fn default() -> Self {
299+
Foo {
300+
alpha: Default::default(),
301+
beta: 1,
302+
}
303+
}
304+
}
305+
```
306+
307+
being generated.
308+
309+
Alternatively, dedicated syntax could be provided [as proposed by @Centril][centril-rfc]:
310+
311+
[centril-rfc]: https://github.com/Centril/rfcs/pull/19
312+
313+
```rust
314+
#[derive(Default)]
315+
struct Foo {
316+
alpha: u8,
317+
beta: u8 = 1,
318+
}
319+
```
320+
321+
If consensus can be reached on desired bounds, there should be no technical restrictions on
322+
permitting the `#[default]` attribute on a `#[non_exhaustive]` variant.
323+
324+
## Clearer documentation and more local reasoning
325+
326+
Providing good defaults when such exist is part of any good design that makes a physical tool, UI
327+
design, or even data-type more ergonomic and easily usable. However, that does not mean that the
328+
defaults provided can just be ignored and that they need not be understood. This is especially the
329+
case when you are moving away from said defaults and need to understand what they were. Furthermore,
330+
it is not too uncommon to see authors writing in the documentation of a data-type that a certain
331+
value is the default.
332+
333+
All in all, the defaults of a data-type are therefore important properties. By encoding the defaults
334+
right where the data-type is defined gains can be made in terms of readability particularly with
335+
regard to the ease of skimming through code. In particular, it is easier to see what the default
336+
variant is if you can directly look at the `rustdoc` page and read the previous snippet, which would
337+
let you see the default variant without having to open up the code of the `Default` implementation.
338+
339+
## `Error` trait and more
340+
341+
As this is the first derive macro that includes an attribute, this may open the flood gates with
342+
regard to permitting additional macros with attributes. Crates such as `thiserror` could be, in some
343+
form or another, upstreamed to the standard library as `#[derive(Error)]`, `#[derive(Display)]` or
344+
more.

0 commit comments

Comments
 (0)