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