Allow #[derive(Default)] on enum variants with data.
Previously, only unit enum variants were allowed to derive Default, by marking
them with #[default]. This feature extens this support to tuple and struct
enum variants with fields when they all implement Default. By extension this
also means that tuple and struct enum variants with no fields are also suitable
to be marked with #[default].
Motivation
Currently, #[derive(Default)] is not usable on enum variants with data. To
rectify this situation, we expand the existing #[default] attribute
implementation to support tuple and struct variants.
This allows you to use #[derive(Default)] on enums wherefore you can now write:
This feature allows for more cases where Default can be derived, instead of
explicitly implemented. This reduces the verbosity of Rust codebases.
Guide-level explanation
In the same way that structs can be annotated with #[derive(Default)]:
which expands to:
The same annotation on an enum with a variant annotated with #[default]:
expands to:
Because the expanded code calls Default::default(), if the fields do not
implement Default the compiler will emit an appropriate error pointing at the
field that doesn't meet its requirement.
error[E0277]: the trait bound `S: Default` is not satisfied
--> src/main.rs:4:5
|
2 | #[derive(Default)]
| ------- in this derive macro expansion
3 | enum Foo {
3 | Bar {
4 | x: S,
| ^^^^ the trait `Default` is not implemented for `S`
|
= note: this error originates in the derive macro `Default` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider annotating `S` with `#[derive(Default)]`
|
1 + #[derive(Default)]
2 | struct S;
|
Reference-level explanation
In rustc_builtin_macros/src/deriving/default.rs, we change
extract_default_variant to not filter only on VariantData::Unit, and
default_enum_substructure to expand the impl in a similar way to
default_struct_substructure.
This expands on RFC-3107. No other changes are needed.
Drawbacks
The usual drawback of increasing the complexity of the implementation applies.
However, the degree to which complexity is increased is not substantial. If
anything, the complexity of the concepts needed to be understood is reduced, as
there are fewer special cases users need to keep in mind when using
#[derive(Default)], as well as allow us to remove impl Defaults from the
standard library.
The same issue highlighted on
RFC-3107 of current #[derive(Default)] on structs producing impls with
incorrect bounds (non-perfect derives) applies to this proposal as well.
Rationale and alternatives
As shown by the existence of derivative and smart-default, there is a
desire to fill this perceived gap in flexibility that the built-in
#[derive(Default)] support has. We can do nothing and let the ecosystem sort
this gap out.
Prior art
Procedural macros
There are a number of crates which to varying degrees afford macros for default field values and associated facilities.
#[derive(Derivative)]
The crate derivative provides the #[derivative(Default)] attribute. With it, you may write:
Contrast this with the equivalent in the style of this RFC:
Like in this RFC, derivative allows you to derive Default for enums. The
syntax used in the macro is #[derivative(Default)] whereas the RFC provides
uses the already existing #[default] annotation.
#[derive(SmartDefault)]
The smart-default provides #[derive(SmartDefault)] custom derive macro. It functions similarly
to derivative but is specialized for the Default trait. With it, you can write:
-
There is no trait
SmartDefaulteven though it is being derived. This works because#[proc_macro_derive(SmartDefault)]is in fact not tied to any trait. That#[derive(Serialize)]refers to the same trait as the name of the macro is from the perspective of the language's static semantics entirely coincidental.However, for users who aren't aware of this, it may seem strange that
SmartDefaultshould derive for theDefaulttrait.
Unresolved questions
- Should we wait until perfect derives are addressed first?
- Should
#[default]be allowed on tuple and struct enum variants with no fields?
Future possibilities
Overriding default values
RFC-3681 already proposes supporting the definition of struct and struct enum
variant field default values, that can be used by #[derive(Default)] to
override the use of Default::default(). These two RFCs interact nicely with
each other.