Skip to content

Latest commit

 

History

History
272 lines (212 loc) · 7.45 KB

File metadata and controls

272 lines (212 loc) · 7.45 KB

Summary

Allow #[derive(Default)] on enum variants with data.

#[derive(Default)]
enum Foo {
    #[default]
    Bar {
        x: Option<i32>,
        y: Option<i32>,
    },
    Baz,
}

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:

#[derive(Default)]
enum Padding {
    #[default]
    Space {
        n: i32,
    },
    None,
}

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)]:

#[derive(Default)]
struct Bar {
    x: Option<i32>,
    y: Option<i32>,
}

which expands to:

impl Default for Bar {
    fn default() -> Bar {
        Bar {
            x: Default::default(),
            y: Default::default(),
        }
    }
}

The same annotation on an enum with a variant annotated with #[default]:

#[derive(Default)]
enum Foo {
    #[default]
    Bar {
        x: Option<i32>,
        y: Option<i32>,
    },
    Baz,
}

expands to:

impl Default for Foo {
    fn default() -> Foo {
        Foo::Bar {
            x: Default::default(),
            y: Default::default(),
        }
    }
}

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:

#[derive(Derivative)]
#[derivative(Default)]
enum Foo {
    #[derivative(Default)]
    Bar {
        value: Option<i32>,
    },
    Baz,
}

Contrast this with the equivalent in the style of this RFC:

#[derive(Default)]
enum Foo {
    #[default]
    Bar {
        value: Option<i32>,
    },
    Baz,
}

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:

#[derive(SmartDefault)]
enum Foo {
    #[default]
    Bar {
        value: Option<i32>,
    },
    Baz,
}
  • There is no trait SmartDefault even 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 SmartDefault should derive for the Default trait.

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.

#[derive(Default)]
enum Foo {
    #[default]
    Bar {
        value: i32 = 42,
    },
    Baz,
}