Skip to content

Commit 1304d15

Browse files
committed
#[derive(Default)] on enum variants with fields
Support the following: ```rust enum Foo { #[default] Bar { x: Option<i32>, y: Option<i32>, }, Baz, } ```
1 parent 46781d0 commit 1304d15

File tree

1 file changed

+267
-0
lines changed

1 file changed

+267
-0
lines changed
Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
- Feature Name: `derive-default-enum-with-fields`
2+
- Start Date: 2024-08-25
3+
- RFC PR: [rust-lang/rfcs#3683](https://github.com/rust-lang/rfcs/pull/3683)
4+
- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000)
5+
6+
# Summary
7+
[summary]: #summary
8+
9+
Allow `#[derive(Default)]` on `enum` variants with data.
10+
11+
```rust
12+
#[derive(Default)]
13+
enum Foo {
14+
#[default]
15+
Bar {
16+
x: Option<i32>,
17+
y: Option<i32>,
18+
},
19+
Baz,
20+
}
21+
```
22+
23+
Previously, only unit enum variants were allowed to derive `Default`, by marking
24+
them with `#[default]`. This feature extens this support to tuple and struct
25+
enum variants with fields when they all implement `Default`. By extension this
26+
also means that tuple and struct enum variants with no fields are also suitable
27+
to be marked with `#[default]`.
28+
29+
# Motivation
30+
[motivation]: #motivation
31+
32+
Currently, `#[derive(Default)]` is not usable on `enum` variants with data. To
33+
rectify this situation, we expand the existing `#[default]` attribute
34+
implementation to support tuple and struct variants.
35+
36+
This allows you to use #[derive(Default)] on enums wherefore you can now write:
37+
38+
```rust
39+
#[derive(Default)]
40+
enum Padding {
41+
#[default]
42+
Space {
43+
n: i32,
44+
},
45+
None,
46+
}
47+
```
48+
49+
This feature allows for more cases where `Default` can be derived, instead of
50+
explicitly implemented. This reduces the verbosity of Rust codebases.
51+
52+
# Guide-level explanation
53+
[guide-level-explanation]: #guide-level-explanation
54+
55+
In the same way that `struct`s can be annotated with `#[derive(Default)]`:
56+
57+
```rust
58+
#[derive(Default)]
59+
struct Bar {
60+
x: Option<i32>,
61+
y: Option<i32>,
62+
}
63+
```
64+
65+
which expands to:
66+
67+
```rust
68+
impl Default for Bar {
69+
fn default() -> Bar {
70+
Bar {
71+
x: Default::default(),
72+
y: Default::default(),
73+
}
74+
}
75+
}
76+
```
77+
78+
The same annotation on an `enum` with a variant annotated with `#[default]`:
79+
80+
```rust
81+
#[derive(Default)]
82+
enum Foo {
83+
#[default]
84+
Bar {
85+
x: Option<i32>,
86+
y: Option<i32>,
87+
},
88+
Baz,
89+
}
90+
```
91+
92+
expands to:
93+
94+
```rust
95+
impl Default for Foo {
96+
fn default() -> Foo {
97+
Foo::Bar {
98+
x: Default::default(),
99+
y: Default::default(),
100+
}
101+
}
102+
}
103+
```
104+
105+
Because the expanded code calls `Default::default()`, if the fields do not
106+
implement `Default` the compiler will emit an appropriate error pointing at the
107+
field that doesn't meet its requirement.
108+
109+
```
110+
error[E0277]: the trait bound `S: Default` is not satisfied
111+
--> src/main.rs:4:5
112+
|
113+
2 | #[derive(Default)]
114+
| ------- in this derive macro expansion
115+
3 | enum Foo {
116+
3 | Bar {
117+
4 | x: S,
118+
| ^^^^ the trait `Default` is not implemented for `S`
119+
|
120+
= note: this error originates in the derive macro `Default` (in Nightly builds, run with -Z macro-backtrace for more info)
121+
help: consider annotating `S` with `#[derive(Default)]`
122+
|
123+
1 + #[derive(Default)]
124+
2 | struct S;
125+
|
126+
```
127+
128+
# Reference-level explanation
129+
[reference-level-explanation]: #reference-level-explanation
130+
131+
[`default_enum_substructure`]: https://github.com/rust-lang/rust/blob/6bb4656ee2ad88425917e3d4ad7ec11a033f181c/compiler/rustc_builtin_macros/src/deriving/default.rs#L96C4-L96C29
132+
133+
[`extract_default_variant`]: https://github.com/rust-lang/rust/blob/6bb4656ee2ad88425917e3d4ad7ec11a033f181c/compiler/rustc_builtin_macros/src/deriving/default.rs#L154C4-L154C27
134+
135+
[`default_struct_substructure`]: https://github.com/rust-lang/rust/blob/6bb4656ee2ad88425917e3d4ad7ec11a033f181c/compiler/rustc_builtin_macros/src/deriving/default.rs#L63C4-L63C31
136+
137+
In `rustc_builtin_macros/src/deriving/default.rs`, we change
138+
[`extract_default_variant`] to not filter *only* on `VariantData::Unit`, and
139+
[`default_enum_substructure`] to expand the `impl` in a similar way to
140+
[`default_struct_substructure`].
141+
142+
[RFC-3107]: https://rust-lang.github.io/rfcs/3107-derive-default-enum.html.
143+
144+
This expands on [RFC-3107]. No other changes are needed.
145+
146+
# Drawbacks
147+
[drawbacks]: #drawbacks
148+
149+
[perfect derives]: https://smallcultfollowing.com/babysteps/blog/2022/04/12/implied-bounds-and-perfect-derive/
150+
151+
The usual drawback of increasing the complexity of the language applies. However, the degree to which complexity is increased is not substantial.
152+
153+
[The same](https://github.com/rust-lang/rust/issues/26925) issue highlighted on
154+
[RFC-3107] of current `#[derive(Default)]` on `struct`s producing `impl`s with
155+
incorrect bounds (non-[perfect derives]) applies to this proposal as well.
156+
157+
# Rationale and alternatives
158+
[rationale-and-alternatives]: #rationale-and-alternatives
159+
160+
[`derivative`]: https://crates.io/crates/derivative
161+
162+
[`smart-default`]: https://crates.io/crates/smart-default
163+
164+
As shown by the existence of [`derivative`] and [`smart-default`], there is a
165+
desire to fill this perceived gap in flexibility that the built-in
166+
`#[derive(Default)]` support has. We can do nothing and let the ecosystem sort
167+
this gap out.
168+
169+
170+
# Prior art
171+
[prior-art]: #prior-art
172+
173+
## Procedural macros
174+
175+
There are a number of crates which to varying degrees afford macros for default field values and
176+
associated facilities.
177+
178+
179+
### `#[derive(Derivative)]`
180+
181+
[`derivative`]: https://crates.io/crates/derivative
182+
183+
The crate [`derivative`] provides the `#[derivative(Default)]` attribute. With it, you may write:
184+
185+
```rust
186+
#[derive(Derivative)]
187+
#[derivative(Default)]
188+
enum Foo {
189+
#[derivative(Default)]
190+
Bar {
191+
value: Option<i32>,
192+
},
193+
Baz,
194+
}
195+
```
196+
197+
Contrast this with the equivalent in the style of this RFC:
198+
199+
```rust
200+
#[derive(Default)]
201+
enum Foo {
202+
#[default]
203+
Bar {
204+
value: Option<i32>,
205+
},
206+
Baz,
207+
}
208+
```
209+
210+
Like in this RFC, `derivative` allows you to derive `Default` for `enum`s. The
211+
syntax used in the macro is `#[derivative(Default)]` whereas the RFC provides
212+
uses the already existing `#[default]` annotation.
213+
214+
### `#[derive(SmartDefault)]`
215+
216+
[`smart-default`]: https://crates.io/crates/smart-default
217+
218+
The [`smart-default`] provides `#[derive(SmartDefault)]` custom derive macro. It functions similarly
219+
to `derivative` but is specialized for the `Default` trait. With it, you can write:
220+
221+
```rust
222+
#[derive(SmartDefault)]
223+
enum Foo {
224+
#[default]
225+
Bar {
226+
value: Option<i32>,
227+
},
228+
Baz,
229+
}
230+
```
231+
232+
- There is no trait `SmartDefault` even though it is being derived. This works because
233+
`#[proc_macro_derive(SmartDefault)]` is in fact not tied to any trait. That `#[derive(Serialize)]`
234+
refers to the same trait as the name of the macro is from the perspective of the language's static
235+
semantics entirely coincidental.
236+
237+
However, for users who aren't aware of this, it may seem strange that `SmartDefault` should derive
238+
for the `Default` trait.
239+
240+
# Unresolved questions
241+
[unresolved-questions]: #unresolved-questions
242+
243+
- Should we wait until [perfect derives] are addressed first?
244+
- Should `#[default]` be allowed on tuple and struct enum variants with no fields?
245+
246+
# Future possibilities
247+
[future-possibilities]: #future-possibilities
248+
249+
## Overriding default values
250+
251+
[RFC-3681]: https://github.com/rust-lang/rfcs/pull/3681
252+
253+
[RFC-3681] already proposes supporting the definition of struct and struct enum
254+
variant field default values, that can be used by `#[derive(Default)]` to
255+
override the use of `Default::default()`. These two RFCs interact nicely with
256+
each other.
257+
258+
```rust
259+
#[derive(Default)]
260+
enum Foo {
261+
#[default]
262+
Bar {
263+
value: i32 = 42,
264+
},
265+
Baz,
266+
}
267+
```

0 commit comments

Comments
 (0)