|  | 
|  | 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