# Pre-RFC: User-provided default field values

**URL:** <https://internals.rust-lang.org/t/pre-rfc-user-provided-default-field-values/15877>\
**Category:** language design\
**Created:** [December 29, 2021, 6:59pm UTC](https://internals.rust-lang.org/t/pre-rfc-user-provided-default-field-values/15877 "2021-12-29T18:59:13Z")\
**Posts on this page:** 20\
**Page:** 2

<div class="post-metadata">

**Author:** ![georgebarwood](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/georgebarwood/32/8584_2.png) [@georgebarwood](https://internals.rust-lang.org/u/georgebarwood)\
**Post date:** [January 1, 2022, 5:40pm UTC](https://internals.rust-lang.org/t/pre-rfc-user-provided-default-field-values/15877/21 "2022-01-01T17:40:32Z")

</div>

Sorry to be negative, but I think that the costs from adding any new language feature outweigh the benefits here. I am not even convinced it's an improvement, it's one more thing to be learned for anyone learning the language ( including the restriction to constants ).

Generally speaking, I think the bar for new language features should be set high, and this proposal does not clear that bar.

---

<div class="post-metadata">

**Author:** ![steffahn](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/steffahn/32/13288_2.png) [@steffahn](https://internals.rust-lang.org/u/steffahn)\
**Post date:** [January 1, 2022, 11:08pm UTC](https://internals.rust-lang.org/t/pre-rfc-user-provided-default-field-values/15877/22 "2022-01-01T23:08:08Z")

</div>

The feature of being able to skip initializing some fields in a constructor expression is quite useful, I believe. The `field: Type = value` syntax the seems quite appropriate for such a language feature, and _once_ this feature exists, it _could_ be a useful idea to be able to use this syntax also for some macros.

Under this view, I would agree that _without_ the default field values feature that allows you to do `let ascribe = ExprType { expr: make_expr(), ty, .. };` style initialization, it's quite reasonable to seriously question the introduction of any new syntax.

Now assuming that we _do_ get default field values, it seems like a reasonable logical thing to do to separate that proposal from the usage of the syntax in macros, and only do the latter as a subsequent proposal/feature. However, that's not possible _if_ we want behavior such as

```rust
#[derive(Default)]
struct Foo { field: u32 = 42 }

```

to use the value `42` for `field` in the `Default` implementation, instead of the value `u32::default()`. Since we don't want to introduce breaking changes, the only way to introduce field default values, without also immediately deciding how the interaction with `derive(Default)` is supposed to work, is to have `derive(Default)` not support `field: Type = value` syntax _at all_ in the meantime.

There'd also be no way to use the `#[derive(Default)]` in a way that _ignores_ the `= value`, even though this syntax's main purpose is only about the meaning of writing `Foo { .. }`.

For `Default` this probably isn't a huge problem; who wouldn't want `Foo { .. }` and `Foo::default()` to be the same value? And in the odd case where you don't want it, you can always write your own `impl Default for Foo`.

* * *

(From here on, this is not really "just" an answer to @H2CO3 anymore, but brings up new, fairly unrelated points...)

I'm bringing up these points also because they apply to other macros, too. Except none of those have the privilege that `Default` has: the `= value` support for `Default` macro can be introduced _together_ with the default field values feature, so breaking changes can be avoided. The same is not a possibility for other macros. There's two ways to solve the problem of breakage:

- the macro still requires an attribute on a field if that field's default value in `= value` syntax should be used for the macro
- the macro just doesn't support the new `= value` syntax in a struct/enum it's applied to at all (users will need to update to a new version of the crate providing the macro that _does_ support it)

however, unlike with `Default`, third-party macro authors don't actually have any way to decide between these two alternatives; either their macro already accepts (and ignores) the new fields, or it doesn't. Only in the latter case can they actually use the `= value` expression on a field in a meaningful way without also introducing some new syntax for it. (Well, I guess at least in this case they _do_ have the ability to decide something after all.)

Now, many macros use [`syn`](https://docs.rs/syn), and most macros will not want to care about any new `field: Type = value` syntax, so it would make a lot of sense if `syn` just starts accepting this syntax in a non-breaking manner. However, this means that macros that _do_ want to add some way to incorporate the new syntax will fall into the first category above, so they _need_ to add this support it in a non-breaking manner.

`serde` and `structopt` both use `syn`. So probably for `serde`, something like

```rust
#[derive(Deserialize)
struct Foo {
    #[serde(default)
    field: u32 = 42,
}

```

would still use a default of `0`, and you'd need _new_ syntax, for example something like

```rust
#[derive(Deserialize)
struct Foo {
    #[serde(default = _)
    field: u32 = 42,
}

```

to use the provided default value. Arguably, the first case _is_ quite confusing; to avoid confusion, `serde` should probably (somehow? maybe we'd be getting [`proc_macro_diagnostics`](https://github.com/rust-lang/rust/issues/54140) stable befor this feathre..) add a warning for this case, requiring the user to rewrite the thing either to

```rust
 #[serde(default = "Default::default()")

```

or

```rust
 #[serde(default = _)

```

for improved clarity.

~~Similar considerations apply to `structopt`, though they'd have (unlike `serde`) the realistic option of releasing a breaking change in a new major version, so that ...~~ oh _ **wait** _ I just realized that `structopt` _only_ supports string literals as default values, because those are passed to a `clap` API that always expects a `&str`. That means that this feature is _completely_ useless for `structopt` because the types don't match; unless they'd want to start supporting turning certain literals (like numbers) into string literals (but this still doesn't scale at all to any other more complex types). And in any case, it would be unusable for `arg: String` fields, which are probably fairly common.

* * *

While considering `syn`, I also noticed that it seems like a pretty hard thing to add these default value declaration to its API in a non-breaking manner.1 Maybe it's even impossible? In which case the discussion above and its conclusions (like that like that `serde` _has_ to add new syntax such as e.g. `serde(default = _)`) becomes irrelevant, and OTOH, any worst-case, any macro that doesn't even care about the new `= value` stuff would presumably need to update to a new `2.0` version of syn?

I'd be interested in hearing what @dtolnay thinks about this.

1The [`Field`](https://docs.rs/syn/1/syn/struct.Field.html): struct doesn't really leave room for the `= value` part in a `struct`/`enum` item. The `Foo { .. }` expressions seem less problematic as currently, [`ExprStruct`](https://docs.rs/syn/1/syn/struct.ExprStruct.html) has the already-existing `..rest` thing in two separate `Option<_>`s already. (I don't actually know why it's like that.)

---

<div class="post-metadata">

**Author:** ![InfernoDeity](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/infernodeity/32/7588_2.png) [@InfernoDeity](https://internals.rust-lang.org/u/InfernoDeity)\
**Post date:** [January 1, 2022, 11:42pm UTC](https://internals.rust-lang.org/t/pre-rfc-user-provided-default-field-values/15877/23 "2022-01-01T23:42:45Z")

</div>

> [@jhpratt](#):
>
> # Prior art
> 
> ???

As I mentioned in the zulip thread, this is fairly similar to C++ Default Member Initializers, so I might indicate that as prior art.

---

<div class="post-metadata">

**Author:** ![H2CO3](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/h2co3/32/2849_2.png) [@H2CO3](https://internals.rust-lang.org/u/H2CO3)\
**Post date:** [January 2, 2022, 8:16am UTC](https://internals.rust-lang.org/t/pre-rfc-user-provided-default-field-values/15877/24 "2022-01-02T08:16:55Z")

</div>

> [@steffahn](#):
>
> The feature of being able to skip initializing some fields in a constructor expression is quite useful, I believe. The `field: Type = value` syntax the seems quite appropriate for such a language feature, and _once_ this feature exists, it _could_ be a useful idea to be able to use this syntax also for some macros.

But the converse argument could also be made… It could _just_ be a `#[derive(Default)]` attribute _first,_ and then everyone could continue writing `Foo { field1: expr1, ..Foo::default() }`. This requires no changes to either the language or the idioms and accomplishes the same result – so then why bother?

---

<div class="post-metadata">

**Author:** ![PoignardAzur](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/poignardazur/32/6464_2.png) [@PoignardAzur](https://internals.rust-lang.org/u/PoignardAzur)\
**Post date:** [January 2, 2022, 12:43pm UTC](https://internals.rust-lang.org/t/pre-rfc-user-provided-default-field-values/15877/25 "2022-01-02T12:43:33Z")

</div>

Something that hasn't been mentioned in the debate so far is that field default currently aren't even _syntactically_ allowed. Eg the following is a compile error:

```rust
#[cfg(FALSE)]
struct Foobar {
    foo: i32 = 0
}

```

Instead I believe it should be like `unsafe mod` : syntactically valid, but semantically rejected, so it can be used in attribute macros.

Something we could do is only allow the most restrictive case semantically - so only allow const values that match the attribute type - and expect authors who want to more complex patterns to use non-derive attribute macros instead, eg:

```rust
#[derive_builder]
struct Lorem {
    ipsum: String,
    // Custom defaults can delegate to helper methods
    // and pass errors to the enclosing `build()` method via `?`.
    dolor: String = self.default_dolor()?,
}

```

... And, okay, now that I write it out, I realize this example isn't that convenient.

But generally speaking, I think there's something to be said for adding a feature that's as small and forward-compatible as possible, and then expanding from there. If we can cover 90% of use cases with an MVP feature, the remaining 10% will stand out more.

---

<div class="post-metadata">

**Author:** ![elidupree](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/elidupree/32/4304_2.png) [@elidupree](https://internals.rust-lang.org/u/elidupree)\
**Post date:** [January 2, 2022, 2:18pm UTC](https://internals.rust-lang.org/t/pre-rfc-user-provided-default-field-values/15877/26 "2022-01-02T14:18:35Z")

</div>

> [@steffahn](#):
>
> `serde` and `structopt` both use `syn` . So probably for `serde` , something like
> 
> ```rust
> #[derive(Deserialize)
> struct Foo {
> #[serde(default)
> field: u32 = 42,
> }
> 
> ```
> 
> would still use a default of `0` , and you'd need _new_ syntax, for example something like
> 
> ```rust
> #[derive(Deserialize)
> struct Foo {
> #[serde(default = _)
> field: u32 = 42,
> }
> 
> ```
> 
> to use the provided default value. Arguably, the first case _is_ quite confusing;

There's another backwards-compatible option for serde here: When you have `field: u32 = 42`, serde could make `field` have a serde-default value of `42` when there _isn't_ any serde attribute specified, and require a new attribute like `#[serde(no_default)]` if you wanted it to _not_ have a default value for serde purposes. (As you say, `#[serde(default)]` would still have to use `u32::default()` instead, and should introduce a warning.) Not sure whether this is better, but it seemed worth mentioning.

---

<div class="post-metadata">

**Author:** ![steffahn](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/steffahn/32/13288_2.png) [@steffahn](https://internals.rust-lang.org/u/steffahn)\
**Post date:** [January 2, 2022, 2:29pm UTC](https://internals.rust-lang.org/t/pre-rfc-user-provided-default-field-values/15877/27 "2022-01-02T14:29:29Z")

</div>

I implicitly assumed that

```rust
#[derive(Default, Deserialize)
struct Foo {
    field: u32 = 42,
}

```

would always be expected to _require_ `field` being present for deserialization, as anything else seems quite surprising to me. After all, I'm specifying the `= 42` for an easy custom `Debug` implementation, and `Foo { .. }` constructor calls; it's not at all clear that I'd want to support new formats for deserialization. (Also, even if what you described _was_ desired behavior, I don't quite see how this makes a difference for backwards-compatibility concerns.)

* * *

_Edit:_ I just re-read your answer and only now fully realized the mention of a `#[serde(no_default)]` attribute. While I disagree for the use-case of serde, and am not convinced that this relates to backwards-compatibility; I _do_ agree that it's worth pointing out that

- for a third-party derive macro, not necessarily `serde::Deserialize` though
- if the goal is to keep the user's ability to choose whether or not a default should be present
- if it's the common case to activate the "default attribute" whenever possible, and it doesn't seem surprising to make `fiel: Type = value` automatically enable it
- then using an opt-out attribute instead of an opt-in attribute is indeed a viable API option

Assuming `syn` doesn't manage to add in `= value` support in a non-breaking manner, this way of handling (as well as any other way of handling) `= value` on fields is even an option that's a non-breaking change. (I.e. under the assumption that currently, the macro would just error when faced with `= value` syntax.)

---

<div class="post-metadata">

**Author:** ![CAD97](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/cad97/32/3460_2.png) [@CAD97](https://internals.rust-lang.org/u/CAD97)\
**Post date:** [January 2, 2022, 3:09pm UTC](https://internals.rust-lang.org/t/pre-rfc-user-provided-default-field-values/15877/28 "2022-01-02T15:09:32Z")

</div>

This point isn't quite true in the face of private members. With language-level field defaults, private members could be defaulted constructed with `{ field, .. }` syntax, whereas `{ field, ..default }` FRU syntax requires all fields to be public.

Whether mixing public and private fields and providing construction in this manner is a good API choice is a different discussion, but it is enabled by field-level defaulting.

---

<div class="post-metadata">

**Author:** ![scottmcm](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/scottmcm/32/2355_2.png) [@scottmcm](https://internals.rust-lang.org/u/scottmcm)\
**Post date:** [January 2, 2022, 5:48pm UTC](https://internals.rust-lang.org/t/pre-rfc-user-provided-default-field-values/15877/29 "2022-01-02T17:48:48Z")

</div>

> [@CAD97](#):
>
> whereas `{ field, ..default }` FRU syntax requires all fields to be public.

It also requires all fields to _have_ defaults. A huge advantage of the default fields thing is that you can still have some fields _without_ defaults, which are required to be specified in the struct initializer expression.

---

<div class="post-metadata">

**Author:** ![steffahn](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/steffahn/32/13288_2.png) [@steffahn](https://internals.rust-lang.org/u/steffahn)\
**Post date:** [January 2, 2022, 8:00pm UTC](https://internals.rust-lang.org/t/pre-rfc-user-provided-default-field-values/15877/30 "2022-01-02T20:00:23Z")

</div>

> [@CAD97](#):
>
> Whether mixing public and private fields and providing construction in this manner is a good API choice is a different discussion, but it is enabled by field-level defaulting.

I think adding a private dummy field with default value would be the correct way of forcing all users of your struct to always construct it with `Foo { field_a, field_b, .. }` syntax; never with `Foo { field_a, field_b }`, which in turn has the effect that adding a new public (or private) field with default value to such a struct would be no breaking change. This is - in effect - somewhat similar to `#[non_exhaustive]` on the struct (but `#[non_exhaustive]` is equivalent to adding a private field _without_ default value / allows you to add _any private_ fields to a struct without breakage, so it can't even support "`Foo { field_a, field_b, .. }`"-style construction).

---

<div class="post-metadata">

**Author:** ![kornel](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/kornel/32/2711_2.png) [@kornel](https://internals.rust-lang.org/u/kornel)\
**Post date:** [January 2, 2022, 8:20pm UTC](https://internals.rust-lang.org/t/pre-rfc-user-provided-default-field-values/15877/31 "2022-01-02T20:20:29Z")

</div>

Would it be possible to support different defaults for different macros by supporting some contextual conditional in the expression?

```rust
#[derive(Default, Serialize)]
struct Foo {
   x: u32 = if magic_cfg!(deriving == "Serialize") { 1 } else { 2 },
}

```

My mental model of this is that the default expression would be copy-pasted into different implementations, so each copy would be evaluated in a different context.

---

<div class="post-metadata">

**Author:** ![H2CO3](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/h2co3/32/2849_2.png) [@H2CO3](https://internals.rust-lang.org/u/H2CO3)\
**Post date:** [January 3, 2022, 8:40am UTC](https://internals.rust-lang.org/t/pre-rfc-user-provided-default-field-values/15877/32 "2022-01-03T08:40:27Z")

</div>

> [@CAD97](#):
>
> Whether mixing public and private fields and providing construction in this manner is a good API choice is a different discussion

I think it's absolutely relevant. Doing that (mixed private/public construction) would be _weird,_ and thus I don't find it the least bit covincing to argue for language-level default values with this sort of requirement.

---

<div class="post-metadata">

**Author:** ![H2CO3](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/h2co3/32/2849_2.png) [@H2CO3](https://internals.rust-lang.org/u/H2CO3)\
**Post date:** [January 3, 2022, 8:40am UTC](https://internals.rust-lang.org/t/pre-rfc-user-provided-default-field-values/15877/33 "2022-01-03T08:40:59Z")

</div>

That's wild. Why would you want to change defaults like that? It seems like a disaster waiting to happen.

---

<div class="post-metadata">

**Author:** ![synek317](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/synek317/32/3371_2.png) [@synek317](https://internals.rust-lang.org/u/synek317)\
**Post date:** [January 3, 2022, 9:54am UTC](https://internals.rust-lang.org/t/pre-rfc-user-provided-default-field-values/15877/34 "2022-01-03T09:54:23Z")

</div>

I'm not sure if I understand what kind of issue the syntax `#[default(...)]` / `#[default = ...]` causes?

In my opinion, it matches the language way better than the proposed syntax.

---

<div class="post-metadata">

**Author:** ![kornel](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/kornel/32/2711_2.png) [@kornel](https://internals.rust-lang.org/u/kornel)\
**Post date:** [January 3, 2022, 1:10pm UTC](https://internals.rust-lang.org/t/pre-rfc-user-provided-default-field-values/15877/35 "2022-01-03T13:10:46Z")

</div>

That's a harsh answer.

I was addressing a question raised earlier in the thread about what if you had a field with a ` = 1` default, and wanted a different default for some derive macros.

---

<div class="post-metadata">

**Author:** ![ekuber](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/ekuber/32/1646_2.png) [@ekuber](https://internals.rust-lang.org/u/ekuber)\
**Post date:** [January 4, 2022, 5:27pm UTC](https://internals.rust-lang.org/t/pre-rfc-user-provided-default-field-values/15877/36 "2022-01-04T17:27:17Z")

</div>

> [@synek317](#):
>
> I'm not sure if I understand what kind of issue the syntax `#[default(...)]` / `#[default = ...]` causes?

Those would softly imply that this only affects `derive(Default)`, which would be very confusing when it is actually unrelated. The use of `field: Type = value` does look like `let` statements, so I wouldn't say that this looks syntactically out of place.

> [@H2CO3](#):
>
> It could _just_ be a `#[derive(Default)]` attribute _first,_ and then everyone could continue writing `Foo { field1: expr1, ..Foo::default() }` .

But it _can't_, even if the feature is implemented as an attribute on the field, this is a _new feature_ enabling something that _can't_ be done today. You can't enforce today that `field1` _must_ be user supplied on construction. You _can_ get around this by leveraging the builder pattern.

---

<div class="post-metadata">

**Author:** ![synek317](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/synek317/32/3371_2.png) [@synek317](https://internals.rust-lang.org/u/synek317)\
**Post date:** [January 4, 2022, 5:46pm UTC](https://internals.rust-lang.org/t/pre-rfc-user-provided-default-field-values/15877/37 "2022-01-04T17:46:58Z")

</div>

> [@ekuber](#):
>
> Those would softly imply that this only affects `derive(Default)` , which would be very confusing when it is actually unrelated

I feel I bit lost. The RFC describes "user-provided default field values". Why would it be unrelated to `#[derive(Default)]`?

In my opinion, it is actually the best that it looks very related to `#[derive(Default)]` because it is exactly what it does - configures the output of the derived `Default` implementation. Other crates could, but don't have to, read this attribute as well.

---

<div class="post-metadata">

**Author:** ![ekuber](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/ekuber/32/1646_2.png) [@ekuber](https://internals.rust-lang.org/u/ekuber)\
**Post date:** [January 4, 2022, 5:52pm UTC](https://internals.rust-lang.org/t/pre-rfc-user-provided-default-field-values/15877/38 "2022-01-04T17:52:59Z")

</div>

> [@synek317](#):
>
> I feel I bit lost. The RFC describes "user-provided default field values". Why would it be unrelated to `#[derive(Default)]` ?

Sorry, I am assuming familiarity/conflating with the prior discussion at

> [@Pre-pre-RFC: syntactic sugar for \`Default::default()\`](https://internals.rust-lang.org/t/pre-pre-rfc-syntactic-sugar-for-default-default/13234/75):
>
> Thanks @spacekookie! I know is a long thread so it is a good time to summarize what the RFC I'll be writing will propose, particularly because it is removed from what I originally wrote: Language support for default values on structs through const expressions: struct Car { wheel\_count: usize = 4; } Language support for partially defaulted structs: struct Person { name: String, age: i128 = 0; } Teach #[derive(Default)] to use the default values #[derive(Default)] struct P…

The idea in that thread evolved to the ability to write

```rust
struct Foo {
    mandatory: Type,
    optional: Type = Type::default(),
}
fn main() {
    let _ = Foo { mandatory: Type::foo(), .. };
}

```

which is something that _can't_ be accomplished with `Default` because you can't have partially initialized values in Rust. Adding this syntax along with the semantics described in the linked post reduces the need of crates writing builders in their APIs. If such feature is added to the language, then leveraging that new syntax from `derive(Default)` makes sense in my eyes. If partially defaulted values are never adopted, then instead going for a field attribute that `derive(Default)` consumes and rejecting new syntax would indeed make more sense.

---

<div class="post-metadata">

**Author:** ![synek317](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/synek317/32/3371_2.png) [@synek317](https://internals.rust-lang.org/u/synek317)\
**Post date:** [January 4, 2022, 10:05pm UTC](https://internals.rust-lang.org/t/pre-rfc-user-provided-default-field-values/15877/39 "2022-01-04T22:05:44Z")

</div>

Thank you so much for this context! It makes a lot more sense now.

To be perfectly honest, I'm still not convinced though. I'm not a fan of the different meanings of `Foo { .. }` vs `Foo { ..Default::default() }` and the fact that there are two places where the default values could be set - in the struct definition and in the `Default::default` implementation. I guess this point has been already raised in the pre-pre-RFC you mentioned.

---

<div class="post-metadata">

**Author:** ![atagunov](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/atagunov/32/5877_2.png) [@atagunov](https://internals.rust-lang.org/u/atagunov)\
**Post date:** [January 4, 2022, 10:21pm UTC](https://internals.rust-lang.org/t/pre-rfc-user-provided-default-field-values/15877/40 "2022-01-04T22:21:39Z")

</div>

> [@synek317](#):
>
> two places where the default values could be set - in the struct definition and in the `Default::default` implementation

...but it wouldn't be _that_ hard to learn either 🙂 ...and perhaps everybody could agree to consider it bad manners to make them different

[Previous page](https://internals.rust-lang.org/t/pre-rfc-user-provided-default-field-values/15877.md?page=1)

[Next page](https://internals.rust-lang.org/t/pre-rfc-user-provided-default-field-values/15877.md?page=3)
