Skip to content

Commit bae1c39

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

File tree

1 file changed

+363
-0
lines changed

1 file changed

+363
-0
lines changed

text/0000-derive-enum-default.md

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

0 commit comments

Comments
 (0)