From d81136d7c7a993884a7614094ecf926cbf9f6f5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Thu, 22 Aug 2024 23:45:36 +0000 Subject: [PATCH 01/37] default-field-values: initial version --- text/0000-default-field-values.md | 1767 +++++++++++++++++++++++++++++ 1 file changed, 1767 insertions(+) create mode 100644 text/0000-default-field-values.md diff --git a/text/0000-default-field-values.md b/text/0000-default-field-values.md new file mode 100644 index 00000000000..298458f11ed --- /dev/null +++ b/text/0000-default-field-values.md @@ -0,0 +1,1767 @@ +- Feature Name: `default_field_values` +- Start Date: 2024-08-22 +- RFC PR: https://github.com/rust-lang/rfcs/pull/3681 +- Rust Issue: + +# Summary +[summary]: #summary + +Allow `struct` definitions to provide default values for individual fields and +thereby allowing those to be omitted from initializers. When deriving `Default`, +the provided values will then be used. For example: + +```rust +#[derive(Default)] +struct Pet { + name: Option, // impl Default for Pet will use Default::default() for name + age: i128 = 42, // impl Default for Pet will use the literal 42 for age +} +``` + +These can then be used in the following way with the Functional Record Update syntax, with no value: + +```rust +// Pet { name: Some(""), age: 42 } +let _ = Pet { name: Some(String::new()), .. } +// Compilation error: `name` needs to be specified +let _ = Pet { .. } +// Derived `Default` uses struct field defaults if present: +// Pet { name: None, age: 42 } +let _ = Pet::default(); +// Pet { name: None, age: 42 } +let _ = Pet { ..Pet::default() }; +``` + +# Motivation +[motivation]: #motivation + +## Boilerplate reduction + +### For `struct`s + +[FRU]: https://doc.rust-lang.org/1.31.0/book/ch05-01-defining-structs.html#creating-instances-from-other-instances-with-struct-update-syntax + +Rust allows you to create an instance of a `struct` using the struct literal +syntax `Foo { bar: expr, baz: expr }`. To do so, all fields in the `struct` +must be assigned a value. This makes it inconvenient to create large `struct`s +whose fields usually receive the same values. Struct literals also cannot be +used to initialize `struct`s with fields which are inaccessible due to privacy. +*[Functional record updates (FRU)][FRU]* can reduce noise when a `struct` +derives `Default`, but are also invalid when the `struct` has inaccessible +fields and do not allow the creation of an `impl` where *some* fields are +mandatory. + +To work around these shortcomings, you can create constructor functions: + +```rust +struct Foo { + alpha: &'static str, + beta: bool, + gamma: i32, +} + +impl Foo { + /// Constructs a `Foo`. + fn new(alpha: &'static str, gamma: i32) -> Self { + Self { + alpha, + beta: true, + gamma + } + } +} + +let foo = Foo::new("Hello", 42); +``` + +[`process::Command`]: https://doc.rust-lang.org/stable/std/process/struct.Command.html + +The problem with a constructor is that you need one for each combination +of fields a caller can supply. To work around this, you can use builders, +such as [`process::Command`] in the standard library. +Builders enable more advanced initialization, but require additional boilerplate. +To represent the difference, we can see the dramatic syntactical increase for +semantically small changes: + +```rust +// All fields are mandatory +struct Foo { + alpha: &'static str, + beta: bool, + gamma: i32, +} +``` + +```rust +impl Foo { + /// Constructs a `Foo`. + fn new(alpha: &'static str, gamma: i32) -> Self { + Self { + alpha, + beta: true, + gamma + } + } +} +``` + +```rust +// A builder type that is able to construct a `Foo`, but that will fail at runtime if a field is +// missing. +#[derive(Default)] +struct FooBuilder { + pub alpha: Option<&'static str>, + pub beta: Option, + pub gamma: Option, +} + +impl FooBuilder { + fn new() -> Self { + FooBuilder::default() + } + fn set_alpha(&mut self, alpha: &'static str) -> &mut Self { + self.alpha = Some(alpha); + self + } + fn set_beta(&mut self, beta: &'static str) -> &mut Self { + self.beta = Some(beta); + self + } + fn set_gamma(&mut self, gamma: &'static str) -> &mut Self { + self.gamma = Some(gamma); + self + } + + fn build(self) -> Foo { + Foo { + alpha: self.alpha.unwrap(), + beta: self.beta.unwrap(), + gamma: self.gamma.unwrap_or(0), + } + } +} +``` + +```rust +pub struct Foo { + pub alpha: &'static str, + pub beta: bool, + pub gamma: i32, +} + +// A builder type that is able to construct a `Foo`, but that will fail at compile time if a field +// is missing. +#[derive(Default)] +pub struct FooBuilder { + alpha: Option<&'static str>, + beta: Option, + gamma: Option, +} + +// We provide this `impl` on its own so that `FooBuilder::new()` will work without specifying the +// const parameters. +impl FooBuilder { + fn new() -> FooBuilder { + FooBuilder::default() + } +} + +// The fields can only be set once. Calling `set_alpha` twice will result in a compilation error. +impl FooBuilder { + fn set_alpha(mut self, alpha: &'static str) -> FooBuilder { + self.alpha = Some(alpha); + unsafe { std::mem::transmute(self) } + } +} +impl FooBuilder { + fn set_beta(mut self, beta: bool) -> FooBuilder { + self.beta = Some(beta); + unsafe { std::mem::transmute(self) } + } +} +impl FooBuilder { + fn set_gamma(mut self, gamma: i32) -> FooBuilder { + self.gamma = Some(gamma); + unsafe { std::mem::transmute(self) } + } +} +// If any field is optional, +impl FooBuilder { + fn build(self) -> Foo { // can only be called if all fields have been set + Foo { + alpha: self.alpha.unwrap(), + beta: self.beta.unwrap(), + gamma: self.gamma.unwrap_or(0), // This is an optional field with a default. + } + } +} + +fn main() { + let _ = FooBuilder::new() + .set_alpha("") + .set_beta(false) // If we comment this out, it will no longer compile. + .set_gamma(42) // If we comment this out, it will still compile. + .build(); +} +``` + +All of the above can be represented with the exact same results with struct +field default values, but with much less boilerplate: + +```rust +pub struct Foo { + pub alpha: &'static str, + pub beta: bool, + pub gamma: i32 = 0, +} + +fn main() { + let _ = Foo { + alpha: "", + beta: false, + .. + }; +} +``` + +The builder pattern is quite common in the Rust ecosystem, but as shown above its need is greatly +reduced with `struct` field defaults. + +### `PhantomData` ergonomics + +[nomicon]: https://doc.rust-lang.org/nightly/nomicon/phantom-data.html +[PhantomData]: https://doc.rust-lang.org/stable/std/marker/struct.PhantomData.html + +Say that you want to define our own `Vec` type. As the [nomicon] says, +you need to use [PhantomData] to make sure that drop-checking is sound like so: + +```rust +use std::marker::PhantomData; + +struct Vec { + data: *const T, // *const for variance! + len: usize, + cap: usize, + _marker: PhantomData, +} +``` + +You then go on to define some operations for `Vec`: + +```rust +impl Vec { + pub fn new() -> Self { + Self { + // other fields... + _marker: PhantomData, + } + } + + pub fn with_capacity(capacity: usize) -> Self { + Self { + // other fields... + _marker: PhantomData, + } + } +} +``` + +Here, `_marker: PhantomData` is just provided to satisfy the compiler. +`PhantomData` carries no useful information since it is a ZST. +Therefore, the result of having to add a field of type `PhantomData` +is a regression to ergonomics throughout. With default values you can +improve the situation slightly and provide the value in a single place: + +```rust +use std::marker::PhantomData; + +struct Vec { + data: *const T, // *const for variance! + len: usize, + cap: usize, + _marker: PhantomData = PhantomData, +} +``` + +You then go on to define some operations for `Vec`: + +```rust +impl Vec { + pub fn new() -> Self { + Self { + // other fields... + .. + } + } + + pub fn with_capacity(capacity: usize) -> Self { + Self { + // other fields... + .. + } + } +} +``` + +## `#[derive(Default)]` in more cases + +The `#[derive(..)]` ("custom derive") mechanism works by defining procedural +*macros*. Because they are macros, these operate on abstract *syntax* and +don't have more information available. Therefore, when you `#[derive(Default)]` +on a data type definition as with: + +```rust +#[derive(Default)] +struct Foo { + bar: u8, + baz: String, +} +``` + +it only has the immediate "textual" definition available to it. + +Because Rust currently does not have an in-language way to define default values, +you cannot `#[derive(Default)]` in the cases where you are not happy with the +natural default values that each field's type provides. By extending the syntax +of Rust such that default values can be provided, `#[derive(Default)]` can be +used in many more circumstances and thus boilerplate is further reduced. The +addition of a single field, expands the code written by the `struct` author from +a single `derive` line to a whole `Default` `impl`, which becomes more verbose +linearly with the number of fields. + +## Usage by other `#[derive(..)]` macros + +[`serde`]: https://serde.rs/attributes.html + +Custom derive macros exist that have a notion of or use default values. + +### `serde` + +For example, the [`serde`] crate provides a `#[serde(default)]` attribute that +can be used on `struct`s, and fields. This will use the field's or type's +`Default` implementations. This works well with field defaults; `serde` can +either continue to rely on `Default` implementations in which case this RFC +facilitates specification of field defaults; or it can directly use the default +values provided in the type definition. + +### `structopt` + +Another example is the `structopt` crate with which you can write: + +```rust +#[derive(Debug, StructOpt)] +#[structopt(name = "example", about = "An example of StructOpt usage.")] +struct Opt { + /// Set speed + #[structopt(short = "s", long = "speed", default_value = "42")] + speed: f64, + ... +} +``` + +By having default field values in the language, `structopt` could let you write: + +```rust +#[derive(Debug, StructOpt)] +#[structopt(name = "example", about = "An example of StructOpt usage.")] +struct Opt { + /// Set speed + #[structopt(short = "s", long = "speed")] + speed: f64 = 42, + ... +} +``` + +### `derive_builder` + +[`derive_builder`]: https://docs.rs/derive_builder/0.7.0/derive_builder/#default-values + +A third example comes from the crate [`derive_builder`]. As the name implies, +you can use it to `#[derive(Builder)]`s for your types. An example is: + +```rust +#[derive(Builder, Debug, PartialEq)] +struct Lorem { + #[builder(default = "42")] + pub ipsum: u32, +} +``` + +### Conclusion + +As seen in the previous sections, rather than make deriving `Default` +more magical, by allowing default field values in the language, +user-space custom derive macros can make use of them. + +# Guide-level explanation +[guide-level-explanation]: #guide-level-explanation + +## Providing field defaults + +Consider a data-type such as (1): + +```rust +pub struct Probability { + value: f32, +} +``` + +You'd like encode the default probability value to be `0.5`; +With this RFC now you can provide such a default directly where `Probability` +is defined like so (2): + +```rust +pub struct Probability { + value: f32 = 0.5, +} +``` + +Having done this, you can now construct a `Probability` with a struct +initializer and leave `value` out to use the default (3): + +```rust +let prob = Probability { .. }; +``` + +## Deriving `Default` + +Previously, you might have instead implemented the `Default` trait like so (4): + +```rust +impl Default for Probability { + fn default() -> Self { + Self { value: 0.5 } + } +} +``` + +You can now shorten this to (5): + +```rust +impl Default for Probability { + fn default() -> Self { + Self { .. } + } +} +``` + +However, since you had specified `value: f32 = 0.5` in the definition of +`Probability`, you can take advantage of that to write the more simpler +and more idiomatic (6): + +```rust +#[derive(Default)] +pub struct Probability { + value: f32 = 0.5, +} +``` + +Having done this, a `Default` implementation equivalent to the one in (5) +will be generated for you. + +## More fields + +As you saw in the [summary], you are not limited to a single field and all +fields need not have any defaults associated with them. Instead, you can freely +mix and match. Given the definition of `LaunchCommand` from the [motivation] (7): + +```rust +struct LaunchCommand { + cmd: String, + args: Vec = vec![], + some_special_setting: Option = None, + setting_most_people_will_ignore: Option = None, +} +``` + +you can omit all fields but `cmd` (8): + +```rust +let ls_cmd = LaunchCommand { + cmd: "ls".to_string(), + .. +}; +``` + +You can also elect to override the provided defaults (9): + +```rust +let ls_cmd2 = LaunchCommand { + cmd: "ls".to_string(), + args: vec!["-lah".to_string()], + some_special_setting: make_special_setting(), + // setting_most_people_will_ignore is still defaulted. + .. +}; +``` + +## Default fields values are [`const` context]s + +[`const` context]: https://github.com/rust-lang-nursery/reference/blob/66ef5396eccca909536b91cad853f727789c8ebe/src/const_eval.md#const-context + +As you saw in (7), `Vec::new()`, a function call, was used. +However, this assumes that `Vec::new` is a *`const fn`*. That is, when you +provide a default value `field: Type = value`, the given `value` must be a +*constant expression* such that it is valid in a [`const` context]. +Therefore, you cannot write something like (10): + +```rust +fn launch_missilies() -> Result<(), LaunchFailure> { + authenticate()?; + begin_launch_sequence()?; + ignite()?; + Ok(()) +} + +struct BadFoo { + bad_field: u8 = { + launch_missilies().unwrap(); + 42 + }, +} +``` + +Since launching missiles interacts with the real world and has *side-effects* +in it, it is not possible to do that in a `const` context since it may violate +deterministic compilation. + +## Privacy interactions + +In examples thus far, initialization literal expressions `Foo { bar, .. }` have +been used in the same module as where the constructed type was defined in. + +Let's now consider a scenario where this is not the case (11): + +```rust +pub mod foo { + pub struct Alpha { + beta: u8 = 42, + gamma: bool = true, + } +} + +mod bar { + fn baz() { + let x = Alpha { .. }; + } +} +``` + +Despite `foo::bar` being in a different module than `foo::Alpha` and despite +`beta` and `gamma` being private to `foo::bar`, a Rust compiler will accept +the above snippet. It is legal because when `Alpha { .. }` expands to +`Alpha { beta: 42, gamma: true }`, the fields `beta` and `gamma` are considered +in the context of `foo::Alpha`'s *definition site* rather than `bar::baz`'s +definition site. + +## `#[non_exhaustive]` interactions + +[RFC 2008]: https://github.com/rust-lang/rfcs/blob/master/text/2008-non-exhaustive.md#structs-1 + +[RFC 2008] introduced the attribute `#[non_exhaustive]` that can be placed +on `struct`, `enum`, and `enum` variants. The RFC notes that upon defining +a `struct` in *crate A* such as (12): + +```rust +#[non_exhaustive] +pub struct Config { + pub width: u16, + pub height: u16, +} +``` + +it is **_not_** possible to initialize a `Config` in a different *crate B* (13): + +```rust +let config = Config { width: 640, height: 480 }; +``` + +This is forbidden when `#[non_exhaustive]` is attached because the purpose of +the attribute is to permit adding fields to `Config` without causing a +breaking change. However, the RFC goes on to note that you can pattern match +if you allow for the possibility of having fields be ignored with `..` (14): + +```rust +let Config { width, height, .. } = config; +``` + +Because this RFC gives a way to have default field values, you can now simply +invert the pattern expression and initialize a `Config` like so (15): + +```rust +let config = Config { width, height, .. }; +``` + +## Defaults for `enum`s + +The ability to give fields default values is not limited to `struct`s. +Fields of `enum` variants can also be given defaults (16): + +```rust +enum Ingredient { + Tomato { + color: Color = Color::Red, + taste: TasteQuality, + }, + Onion { + color: Color = Color::Yellow, + } +} +``` + +Given these defaults, you can then proceed to initialize `Ingredient`s +as you did with `struct`s (17): + +```rust +let sallad_parts = vec![ + Ingredient::Tomato { taste: Yummy, .. }, + Ingredient::Tomato { taste: Delicious, color: Color::Green, }, + Ingredient::Onion { .. }, +]; +``` + +Note that `enum` variants have public fields and in today's Rust, +this cannot be controlled with visibility modifiers on variants. + +Furthermore, when `#[non_exhaustive]` is specified directly on an `enum`, +it has no interaction with the defaults values and the ability to construct +variants of said enum. However, as specified by [RFC 2008], `#[non_exhaustive]` +is permitted on variants. When that occurs, the behaviour is the same as if +it had been attached to a `struct` with the same fields. + +### Interaction with `#[default]` + +[`#[default]`]: https://github.com/rust-lang/rfcs/pull/3107 + +It is possible today to specify a [`#[default]` variant] in an enum so that it +can be `#[derive(Default)]`. A variant marked with `#[default]` will use +defaulted fields when present. + +```rust +#[derive(Default)] +enum Ingredient { + Tomato { + color: Color = Color::Red, + taste: TasteQuality, + }, + Onion { + color: Color = Color::Yellow, + }, + #[default] + Lettuce { + color: Color = Color::Green, + }, +} +``` + +Now the compiler does know that `Ingredient::Lettuce` should be considered +the default and will accordingly generate an appropriate implementation of +`Default for Ingredient` (19): + +```rust +impl Default for Ingredient { + fn default() -> Self { + Ingredient::Lettuce { + color: Color::Green, + } + } +} +``` + +## Defaults on tuple `struct`s and tuple `enum` variants + +Default values are only allowed on named fields. There is no syntax provided for +tuple types like `struct S(i32)` or `enum E { V(i32), }`. + +# Reference-level explanation +[reference-level-explanation]: #reference-level-explanation + +## Field default values + +### Grammar + +Let the grammar of record fields in `struct`s and `enum` variants be defined +like so (in the `.lyg` notation): + +```rust +RecordField = attrs:OuterAttr* vis:Vis? name:IDENT ":" ty:Type; +``` + +Then, `RecordField` is changed into: + +```rust +RecordField = attrs:OuterAttr* vis:Vis? name:IDENT ":" ty:Type { "=" def:Expr }?; +``` + +Further, given the following partial definition for the expression grammar: + +```rust +Expr = attrs:OuterAttr* kind:ExprKind; +ExprKind = + | ... + | Struct:{ path:Path "{" attrs:InnerAttr* fields:StructExprFieldsAndBase "}" } + ; + +StructExprFieldsAndBase = + | Fields:{ fields:StructExprField* % "," ","? } + | Base:{ ".." base:Expr } + | FieldsAndBase:{ fields:StructExprField+ % "," "," ".." base:Expr } + ; +StructExprField = attrs:OuterAttr* kind:StructExprFieldKind; +StructExprFieldKind = + | Shorthand:IDENT + | Explicit:{ field:FieldName ":" expr:Expr } + ; +``` + +the rule `StructExprFieldsAndBase` is extended with: + +```rust +StructExprFieldsAndBase =| FieldsAndDefault:{ fields:StructExprField+ % "," "," ".." }; +``` + +### Static semantics + +#### Defining defaults + +Given a `RecordField` where the default is specified, i.e.: + +```rust +RecordField = attrs:OuterAttr* vis:Vis? name:IDENT ":" ty:Type "=" def:Expr; +``` + +all the following rules apply when type-checking: + +1. The expression `def` must be a constant expression. + +2. The expression `def` must unify with the type `ty`. + +When lints check attributes such as `#[allow(lint_name)]` are placed on a +`RecordField`, it also applies to `def` if it exists. + +#### Initialization expressions + +Given an expression `τ` of the form (e.g. `Foo { x: 1, .. }`): + +```rust +path:Path "{" attrs:InnerAttr* fields:StructExprField+ % "," "," ".." "}" +``` + +all the following rules apply when type-checking: + +1. All `fields` in `τ` mentioned must exist in the `struct` or `enum` variant + referenced by `path`. No field in `fields` may be mentioned twice. + +2. For each `field: expr` pair in `fields` of `τ`, + `expr` must unify with the type of `field` in the `struct` + or `enum` variant referenced by `path`. + +3. For each `field: expression` pair in `fields` of `τ`, + `field` must be visible in the context which `τ` occurs in. + +4. For each field `f` in `τ` *not* mentioned in `fields`, a default value must + exist for `f` in the `struct` or `enum` variant referenced by `path`. + +--------------------- + +**Lemma 1:** If `τ` is of a type `σ` where `σ: Sync`, and if all `field: expr` +has an `expr` component which is a valid constant expression, then `τ` is +also a valid constant expression. + +**Proof:** It trivially holds that if all `expr`s in `τ` are +constant expressions and all default values for fields omitted +are also constant expressions, then `τ` is a constant expression. + +**Lemma 2:** If an expression `x` of form `Path { .. }` is of a type `σ` where +`σ: Sync`, then `x` is a constant expression. + +**Proof:** `x` is a special case of `τ` with all fields according to the default +values. Since there are no explicitly provided fields, by Lemma 1 it holds. + +### Dynamic semantics + +Given a well-formed expression `τ` according to the aforementioned form, +the operational semantics is the same as the semantics of an expression +`path { new_fields }` with `new_fields = fields ∪ default_fields` where: + ++ `∪` computes the left-biased union where equal elements are determined + solely on equality of field names. + ++ `fields` are the set of explicitly given `field: expr` pairs. + ++ `default_fields` are the fields in `τ`'s data-type or variant which + have provided default values. + +## `#[derive(Default)]` + +When generating an implementation of `Default` for a `struct` named `$s` on +which `#[derive(Default)]` has been attached, the compiler will omit all fields +which have default values provided in the `struct`. The the associated function +`default` shall then be defined as (where `$f_i` denotes a vector of the fields +of `$s`): + +```rust +fn default() -> Self { + $s { $f_i: Default::default(), .. } +} +``` + +# Drawbacks +[drawbacks]: #drawbacks + +The usual drawback of increasing the complexity of the language applies. +However, the degree to which complexity is increased is not substantial. + +In particular, the syntax `Foo { .. }` mirrors the identical and already +existing pattern syntax. This makes the addition of `Foo { .. }` at worst +low-cost and potentially cost-free. + +The main complexity comes instead from introducing `field: Type = expr`. +However, as seen in the [prior-art], there are several widely-used languages +that have a notion of field / property / instance-variable defaults. +Therefore, the addition is intuitive and thus the cost is seen as limited. As +an implementation detail, `rustc` *already* parses `field: Type = expr` +purely to provide an appropriate diagnostic error: + +``` +error: default values on `struct` fields aren't supported + --> src/lib.rs:2:28 + | +2 | pub alpha: &'static str = "", + | ^^^^^ help: remove this unsupported default value +``` + +# Rationale and alternatives +[rationale-and-alternatives]: #rationale-and-alternatives + +Besides the given [motivation], there are some specific design choices +worthy of more in-depth discussion, which is the aim of this section. + +## Provided associated items as precedent + +While Rust does not have any support for default values for fields or for +formal parameters of functions, the notion of defaults are not foreign to Rust. + +Indeed, it is possible to provide default function bodies for `fn` items in +`trait` definitions. For example: + +```rust +pub trait PartialEq { + fn eq(&self, other: &Rhs) -> bool; + + fn ne(&self, other: &Rhs) -> bool { // A default body. + !self.eq(other) + } +} +``` + +In traits, `const` items can also be assigned a default value. For example: + +```rust +trait Foo { + const BAR: usize = 42; // A default value. +} +``` + +Thus, to extend Rust with a notion of field defaults is not an entirely alien +concept. + +## Pattern matching follows construction + +[dual]: https://en.wikipedia.org/wiki/Duality_(mathematics) + +In mathematics there is a notion of one thing being the *[dual]* of another. +Loosely speaking, duals are often about inverting something. +In Rust, one example of such an inversion is expressions and patterns. + +Expressions are used to *build up* and patterns *break apart*; +While it doesn't hold generally, a principle of language design both in Rust +and other languages with with pattern matching has been that the syntax for +patterns should, to the extent possible, follow that of expressions. + +For example: + ++ You can match on or build up a struct with `Foo { field }`. + For patterns this will make `field` available as a binding + while for expressions the binding `field` will be used to build a `Foo`. + + For a tuple struct, `Foo(x)` will work both for construction and matching. + ++ If you want to be more flexible, both patterns and expressions permit + `Foo { field: bar }`. + ++ You can use both `&x` to dereference and bind to `x` or + construct a reference to `x`. + ++ An array can be constructed with `[a, b, c, d]` and the same is a valid + pattern for destructuring an array. + +The reason why matching should follow construction is that it makes languages +easier to understand; you simply learn the expression syntax and then reuse +it to run the process in reverse. + +In some places, Rust could do a better job than it currently does of adhering to +this principle. In this particular case, the pattern syntax `Foo { a, b: c, .. }` +has no counterpart in the expression syntax. This RFC rectifies this by +permitting `Foo { a, b: c, .. }` as an expression syntax; this is identical +to the expression syntax and thus consistency has been gained. + +However, it is not merely sufficient to use the same syntax for expressions; +the semantics also have to be similar in kind for things to work out well. +This RFC argues that this is the case because in both contexts, `..` indicates +something partially ignorable is going on: "I am *destructuring*/*constructing* +this struct, and by the way there are some more fields I don't care about +*and let's* drop those* / *and let's fill in with default values*". +In a way, the use of `_` to mean both a catch-all pattern and type / value +placeholder is similar to `..`; in the case of `_` both cases indicate something +unimportant going on. For patterns, `_` matches everything and doesn't give +access to the value; for types, the placeholder is just an unbounded inference +variable. + +## On privacy + +This RFC specifies that when field defaults are specified, the desugaring +semantics of `Foo { .. }` spans `field: default_value` with the context in +which the type is defined as opposed to where `Foo { .. }` occurs. +In other words, the following is valid: + +```rust +pub mod alpha { + pub struct Foo { + field: u8 = 42, + } +} + +use alpha::Foo; + +mod beta { + // `field` isn't visible here. + fn bar() -> Foo { + Foo { .. } // OK! + } +} +``` + +By permitting the above snippet, you are able to construct a default value +for a type more ergonomically with `Foo { .. }`. Since it isn't possible for +functions in `beta` to access `field`'s value, the value `42` or any other +remains at all times private to `alpha`. Therefore, privacy, and by extension +soundness, is preserved. + +If a user wishes to keep other modules from constructing a `Foo` with +`Foo { .. }` they can simply add, or keep, one private field without a default. +Situations where this can be important include those where `Foo` is some token +for some resource and where fabricating a `Foo` may prove dangerous or worse +unsound. This is however no different than carelessly adding `#[derive(Default)]`. + +The feature *can* be stabilized *without* the privacy changes, turning the above +snippet into invalid code, in order to expedite the acceptance of this change, +leaving us to revisit this question at a later time once we get further real +world experience. + +## On `const` contexts + +To recap, the expression a default value is computed with must be constant one. +There are many reasons for this restriction: + ++ If *determinism* is not enforced, then just by writing the following snippet, + the condition `x == y` may fail: + + ```rust + let x = Foo { .. }; + let y = Foo { .. }; + ``` + + This contributes to surprising behaviour overall. + + Now you may object with an observation that if you replace `Foo { .. }` with + `make_foo()` then a reader no longer know just from the syntactic form whether + `x == y` is still upheld. This is indeed true. However, there is a general + expectation in Rust that a function call may not behave deterministically. + Meanwhile, for the syntactic form `Foo { .. }` and with default values, + the whole idea is that they are something that doesn't require close attention. + ++ The broader class of problem that non-determinism highlights is that of + *side*-effects. These effects wrt. program behaviour are prefixed with + *"side"* because they happen without being communicated in the type system + or more specifically in the inputs and outputs of a function. + + In general, it is easier to do formal verification of programs that lack + side-effects. While programming with Rust, requirements are usually not + that demanding and robust. However, the same properties that make pure + logic easier to formally verify also make for more *local reasoning*. + + [reasoning footprint]: https://blog.rust-lang.org/2017/03/02/lang-ergonomics.html#implicit-vs-explicit + + _By requring default field values to be `const` contexts, global reasoning + can be avoided. Thus, the [reasoning footprint] for `Foo { .. }` is reduced._ + ++ By restricting ourselves to `const` contexts, you can be sure that default + literals have a degree of *cheapness*. + + While `const` expressions form a turing complete language and therefore + have no limits to their complexity other than being computable, + these expressions are evaluated at *compile time*. + Thus, *`const` expressions cannot have unbounded complexity at run-time*. + At most, `const` expressions can create huge arrays and similar cases; + + Ensuring that `Foo { .. }` remains relatively cheap is therefore important + because there is a general expectation that literal expressions have a small + and predictable run-time cost and are trivially predictable. + This is particularly important for Rust since this is a language that aims + to give a high degree of control over space and time as well as predictable + performance characteristics. + ++ Keeping default values limited to `const` expressions ensures that if + the following situation develops: + + ```rust + // Crate A: + pub struct Foo { + bar: u8 = const_expr, + } + + // Crate B: + const fn baz() -> Foo { + Foo { .. } + } + ``` + + then crate A cannot suddenly, and unawares, cause a semver breakage + for crate B by replacing `const_expr` with `non_const_expr` since + the compiler would reject such a change (see lemmas 1-2). + Thus, enforcing constness gives a helping hand in respecting semantic version. + + Note that if Rust would ever gain a mechanism to state that a + function will not diverge, e.g.: + + ```rust + nopanic fn foo() -> u8 { 42 } // The weaker variant; more easily attainable. + total fn bar() -> u8 { 24 } // No divergence, period. + ``` + + then the same semver problem would manifest itself for those types of + functions. However, Rust does not have any such enforcement mechanism + right now and if it did, it is generally harder to ensure that a function + is total than it is to ensure that it is deterministic; thus, while + it is regrettable, this is an acceptable trade-off. + ++ Finally, note that `const fn`s, when coupled with planned future extensions, + can become quite expressive. For example, it is already planned that `loop`s, + `match`es, `let` statements, and `panic!(..)`s will be allowed. + Another feasible extension in the future is allocation. + + Therefore, constant expressions should be enough to satisfy most expressive + needs. + +## Instead of `Foo { ..Default::default() }` + +As an alternative to the proposed design is either explicitly writing out +`..Default::default()` or extending the language such that `Foo { .. }` becomes +sugar for `Foo { ..Default::default() }`. While the latter idea does not satisfy +any of the [motivation] set out, the former does to a small extent. + +In particular, `Foo { .. }` as sugar slightly improves ergonomics. +However, it has some notable problems: + ++ Because it desugars to `Foo { ..Default::default() }`, it cannot be required + that the expression is a constant one. This carries all the problems noted in + the previous section on why default field values should be a `const` context. + ++ There is no way of implementing a `Default` implementation that has mandatory + fields for users to specify during value construction. + ++ It provides zero improvements to the ergonomics of *specifying* defaults, + only for using them. Arguably, the most important aspect of this RFC is + not the syntax `Foo { .. }` but rather the ability to provide default values + for field. + ++ By extension, the improvement to documentation clarity is lost. + ++ The trait `Default` must now become a `#[lang_item]`. This is a sign of + increasing the overall magic in the system; meanwhile, this proposal makes + the default values provided usable by other custom derive macros. + +Thus in conclusion, while desugaring `..` to `Default::default()` has lower cost, +it also provides significantly less value to the point of not being worth it. + +## `..` is useful as a marker + +One possible change to the current design is to permit filling in defaults +by simply writing `Foo {}`; in other words, `..` is simply dropped from the +expression. + +Among the benefits are: + ++ To enhance ergonomics of initialization further. + ++ To introduce less syntax. + ++ To be more in line with how other languages treat default values. + +Among the drawbacks are: + ++ The syntax `Foo { .. }` is no longer introduced to complement the identical + pattern syntax. As aforementioned, destruction (and pattern matching) + generally attempts to follow construction in Rust. Because of that, + introducing `Foo { .. }` is essentially cost-free in terms of the complexity + budget. It is arguably even cost-negative. + ++ By writing `Foo { .. }`, you have there is explicit indication that default + values are being used; this enhances local reasoning further. + +## Named function arguments with default values + +A frequently requested feature is named function arguments. Today, the way to +design around the lack of these in the language are: + ++ Builder pattern ++ Defining a `struct` "bag-object" where optional fields are set, making users + call functions in the following way: + `foo(mandatory, Optionals { bar: 42, ..Default::default() })` ++ Provide multiple methods: `fn foo(mandatory)` *and* `fn foo_with_bar(mandatory, bar)` + +# Prior art +[prior-art]: #prior-art + +A prior version of this RFC, from which part of the contents in this version +were sourced, exists at https://github.com/Centril/rfcs/pull/19. + +This RFC was informed by a [lenghty discussion in internals.rust-lang.org](https://internals.rust-lang.org/t/pre-pre-rfc-syntactic-sugar-for-default-default/13234/75) +from a few years prior. + +Another prior RFC for the same feature is at https://github.com/rust-lang/rfcs/pull/1806. + +## Other languages + +This selection of languages are not exhaustive; rather, a few notable or +canonical examples are used instead. + +### Java + +In Java it is possible to assign default values, computed by any expression, +to an instance variable; for example, you may write: + +```java +class Main { + public static void main(String[] args) { + new Foo(); + } + + public static int make_int() { + System.out.println("I am making an int!"); + return 42; + } + + static class Foo { + private int bar = Main.make_int(); + } +} +``` + +When executing this program, the JVM will print the following to `stdout`: + +``` +I am making an int! +``` + +Two things are worth noting here: + +1. It is possible to cause arbitrary side effects in the expression that + computes the default value of `bar`. This behaviour is unlike that which + this RFC proposes. + +2. It is possible to construct a `Foo` which uses the default value of `bar` + even though `bar` has `private` visibility. This is because default values + act as syntactic sugar for how the default constructor `Foo()` should act. + There is no such thing as constructors in Rust. However, the behaviour + that Java has is morally equivalent to this RFC since literals are + constructor-like and because this RFC also permits the usage of defaults + for private fields where the fields are not visible. + +### Scala + +Being a JVM language, Scala builds upon Java and retains the notion of default +field values. For example, you may write: + +```scala +case class Person(name: String = make_string(), age: Int = 42) + +def make_string(): String = { + System.out.println("foo"); + "bar" +} + +var p = new Person(age = 24); +System.out.println(p.name); +``` + +As expected, this prints `foo` and then `bar` to the terminal. + +### Kotlin + +Kotlin is similar to both Java and Scala; here too can you use defaults: + +```kotlin +fun make_int(): Int { + println("foo"); + return 42; +} + +class Person(val age: Int = make_int()); + +fun main() { + Person(); +} +``` + +Similar to Java and Scala, Kotlin does also permit side-effects in the default +values because both languages have no means of preventing the effects. + +### C# + +Another language with defaults of the object-oriented variety is C#. +The is behaviour similar to Java: + +```csharp +class Foo { + int bar = 42; +} +``` + +### C++ + +Another language in the object-oriented family is C++. It also affords default +values like so: + +```cpp +#include + +int make_int() { + std::cout << "hello" << std::endl; // As in Java. + return 42; +} + +class Foo { + private: + int bar = make_int(); + public: + int get_bar() { + return this->bar; + } +}; + +int main() { + Foo x; + std::cout << x.get_bar() << std::endl; +} +``` + +In C++ it is still the case that the defaults are usable due to constructors. +And while the language has `constexpr` to enforce the ability to evaluate +something at compile time, as can be seen in the snippet above, no such +requirement is placed on default field values. + +### Swift + +[Swift]: https://docs.swift.org/swift-book/LanguageGuide/Initialization.html + +A language which is closer to Rust is [Swift], and it allows for default values: + +```swift +struct Person { + var age = 42 +} +``` + +This is equivalent to writing: + +```swift +struct Person { + var age: Int + init() { + age = 42 + } +} +``` + +### Agda + +Having defaults for record fields is not the sole preserve of OO languages. +The pure, total, and dependently typed functional programming language Agda +also affords default values. For example, you may write: + +```agda +-- | Define the natural numbers inductively: +-- This corresponds to an `enum` in Rust. +data Nat : Set where + zero : Nat + suc : Nat → Nat + +-- | Define a record type `Foo` with a field named `bar` typed at `Nat`. +record Foo : Set where + bar : Nat + bar = zero -- An optionally provided default value. + +myFoo : Foo +myFoo = record {} -- Construct a `Foo`. +``` + +In contrast to languages such as Java, Agda does not have have a notion of +constructors. Rather, `record {}` fills in the default value. + +[strongly normalizing]: https://en.wikipedia.org/wiki/Normalization_property_(abstract_rewriting) + +Furthermore, Agda is a pure and [strongly normalizing] language and as such, +`record {}` may not cause any side-effects or even divergence. However, +as Agda employs monadic IO in the vein of Haskell, +it is possible to store a `IO Nat` value in the record: + +```agda +record Foo : Set where + bar : IO Nat + bar = do + putStrLn "hello!" + pure zero +``` + +Note that this is explicitly typed as `bar : IO Nat` and that `record {}` won't +actually run the action. To do that, you will need take the `bar` value and run +it in an `IO` context. + +## Procedural macros + +There are a number of crates which to varying degrees afford macros for +default field values and associated facilities. + +### `#[derive(Builder)]` + +A third example comes from the crate [`derive_builder`]. As the name implies, +you can use it to `#[derive(Builder)]`s for your types. An example is: + +```rust +#[derive(Builder, Debug, PartialEq)] +struct Lorem { + #[builder(default = "42")] + pub ipsum: u32, +} +``` + +Under this RFC, the code would be + +```rust +#[derive(Default, Debug, PartialEq)] +struct Lorem { + pub ipsum: u32 = 42, +} +``` + + +### `#[derive(Derivative)]` + +[`derivative`]: https://crates.io/crates/derivative + +The crate [`derivative`] provides the `#[derivative(Default)]` attribute. +With it, you may write: + +```rust +#[derive(Derivative)] +#[derivative(Default)] +struct RegexOptions { + #[derivative(Default(value="10 * (1 << 20)"))] + size_limit: usize, + #[derivative(Default(value="2 * (1 << 20)"))] + dfa_size_limit: usize, + #[derivative(Default(value="true"))] + unicode: bool, +} + +#[derive(Derivative)] +#[derivative(Default)] +enum Foo { + #[derivative(Default)] + Bar, + Baz, +} +``` + +Contrast this with the equivalent in the style of this RFC: + +```rust +#[derive(Default)] +struct RegexOptions { + size_limit: usize = 10 * (1 << 20), + dfa_size_limit: usize = 2 * (1 << 20), + unicode: bool = true, +} + +#[derive(Default)] +enum Foo { + #[default] + Bar, + Baz, +} +``` + +There a few aspects to note: + +1. The signal to noise ratio is high as compared to the notation in this RFC. + Substantial of syntactic overhead is accumulated to specify defaults. + +2. Expressions need to be wrapped in strings, i.e. `value="2 * (1 << 20)"`. + While this is flexible and allows most logic to be embedded, + the mechanism works poorly with IDEs and other tooling. + Syntax highlighting also goes out of the window because the highlighter + has no idea that the string included in the quotes is Rust code. + It could just as well be a poem due to Shakespeare. + At best, a highlighter could use some heuristic. + +3. The macro has no way to enforce that the code embedded in the strings are + constant expressions. It might be possible to fix that but that might + increase the logic of the macro considerably. + +4. Because the macro merely customizes how deriving `Default` works, + it cannot provide the syntax `Foo { .. }`, interact with privacy, + and it cannot provide defaults for `enum` variants. + +5. Like in this RFC, `derivative` allows you to derive `Default` for `enum`s. + The syntax used in the macro is `#[derivative(Default)]` whereas the RFC + provides the more ergonomic and direct notation `#[default]` in this RFC. + +6. To its credit, the macro provides `#[derivative(Default(bound=""))]` + with which you can remove unnecessary bounds as well as add needed ones. + This addresses a deficiency in the current deriving system for built-in + derive macros. However, the attribute solves an orthogonal problem. + The ability to specify default values would mean that `derivative` can + piggyback on the default value syntax due to this RFC. The mechanism for + removing or adding bounds can remain the same. Similar mechanisms could + also be added to the language itself. + +### `#[derive(SmartDefault)]` + +[`smart-default`]: https://crates.io/crates/smart-default + +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: + + +```rust +#[derive(SmartDefault)] +struct RegexOptions { + #[default = "10 * (1 << 20)"] + size_limit: usize, + #[default = "2 * (1 << 20)"] + dfa_size_limit: usize, + #[default = true] + unicode: bool, +} + +#[derive(SmartDefault)] +enum Foo { + #[default] + Bar, + Baz, +} +``` + ++ The signal to noise ratio is still higher as compared to the notation in due + to this RFC. The problems aforementioned from the `derivative` crate with + respect to embedding Rust code in strings also persists. + ++ Points 2-4 regarding `derivative` apply to `smart-default` as well. + ++ The same syntax `#[default]` is used both by `smart-default` and by this RFC. + While it may seem that this RFC was inspired by `smart-default`, this is not + the case. Rather, this RFC's author came up with the notation independently. + That suggests that the notation is intuitive since and a solid design choice. + ++ 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. + +### `#[derive(new)]` + +[`derive-new`]: https://crates.io/crates/derive-new + +The [`derive-new`] crate provides the `#[derive(new)]` custom derive macro. +Unlike the two previous procedural macro crates, `derive-new` does not +provide implementations of `Default`. Rather, the macro facilitates the +generation of `MyType::new` constructors. + +For example, you may write: + +```rust +#[derive(new)] +struct Foo { + x: bool, + #[new(value = "42")] + y: i32, + #[new(default)] + z: Vec, +} + +Foo::new(true); + +#[derive(new)] +enum Enum { + FirstVariant, + SecondVariant(bool, #[new(default)] u8), + ThirdVariant { x: i32, #[new(value = "vec![1]")] y: Vec } +} + +Enum::new_first_variant(); +Enum::new_second_variant(true); +Enum::new_third_variant(42); +``` + +Notice how `#[new(value = "vec![1]")`, `#[new(value = "42")]`, +and `#[new(default)]` are used to provide values that are then omitted +from the respective constructor functions that are generated. + +If you transcribe the above snippet as much as possible to the system proposed +in this RFC, you would get: + +```rust +struct Foo { + x: bool, + y: i32 = 42, + z: Vec = <_>::default(), + // -------------- + // note: assuming some `impl const Default { .. }` mechanism. +} + +Foo { x: true }; + +enum Enum { + FirstVariant, + SecondVariant(bool, u8), // See future possibilities. + ThirdVariant { x: i32, y: Vec = vec![1] } +} + +Enum::FirstVariant; +Enum::SecondVariant(true, 0); +Enum::ThirdVariant { x: 42 }; +``` + +Relative to `#[derive(new)]`, the main benefits are: + ++ No wrapping code in strings, as noted in previous sections. ++ The defaults used can be mixed and matches; it works to request all defaults + or just some of them. + +The constructor functions `new_first_variant(..)` are not provided for you. +However, it should be possible to tweak `#[derive(new)]` to interact with +this RFC so that constructor functions are regained if so desired. + +# Unresolved questions +[unresolved-questions]: #unresolved-questions + +1. What is the right interaction wrt. `#[non_exhaustive]`? + + In particular, if given the following definition: + + ```rust + #[non_exhaustive] + pub struct Config { + pub height: u32, + pub width: u32, + } + ``` + + it is possible to construct a `Config` like so: + + ```rust + let config = Config { width: 640, height: 480, .. }; + ``` + + then adding a field to `Config` can only happen iff that field + is provided a default value. + + This arrangement, while diminishing the usefulness of `#[non_exhaustive]`, + makes the ruleset of the language simpler, more consistent, and also + simplifies type checking as `#[non_exhaustive]` is entirely ignored + when checking `Foo { fields, .. }` expressions. + + As an alternative, users who desire the semantics described above can + omit `#[non_exhaustive]` from their type and instead add a private + defaulted field that has a ZST. + + Resolving this question may be deferred to stabilization of either + `#[non_exhaustive]` or field defaults, whichever comes last. + +# Future possibilities +[future-possibilities]: #future-possibilities + +## Tuple structs and tuple variants + +Although it could, this proposal does not offer a way to specify default values +for tuple struct / variant fields. For example, you may not write: + +```rust +#[derive(Default)] +struct Alpha(u8 = 42, bool = true); + +#[derive(Default)] +enum Ingredient { + Tomato(TasteQuality, Color = Color::Red), + Lettuce, +} +``` + +While well-defined semantics could be given for these positional fields, +there are some tricky design choices; in particular: + ++ It's unclear whether the following should be permitted: + + ```rust + #[derive(Default)] + struct Beta(&'static str = "hello", bool); + ``` + + In particular, the fields with defaults are not at the end of the struct. + A restriction could imposed to enforce that. However, it would also be + useful to admit the above definition of `Beta` so that `#[derive(Default)]` + can make use of `"hello"`. + ++ The syntax `Alpha(..)` as an expression already has a meaning. + Namely, it is sugar for `Alpha(RangeFull)`. Thus unfortunately, + this syntax cannot be used to mean `Alpha(42, true)`. + In some future edition, the syntax `Alpha(...)` (three dots) + could be used for filling in defaults. This would ostensibly entail + adding the pattern syntax `Alpha(...)` as well. + + Until a syntax such as `Alpha(...)` is made available, + tuple structs could be constructed with `Alpha { 0: 43 }`. + However, that is not particularly ergonomic and that diminishes the value + of having field defaults for tuple structs. + +For these reasons, default values for positional fields are not included in +this RFC and are instead left as a possible future extension. + +## Integration with structural records + +[RFC 2584]: https://github.com/rust-lang/rfcs/pull/2584 + +In [RFC 2584] structural records are proposed. +These records are structural like tuples but have named fields. +As an example, you can write: + +```rust +let color = { red: 255u8, green: 100u8, blue: 70u8 }; +``` + +which then has the type: + +```rust +{ red: u8, green: u8, blue: u8 } +``` + +These can then be used to further emulate named arguments. For example: + +```rust +fn open_window(config: { height: u32, width: u32 }) { + // logic... +} + +open_window({ height: 720, width: 1280 }); +``` + +Since this proposal introduces field defaults, the natural combination with +structural records would be to permit them to have defaults. For example: + +```rust +fn open_window(config: { height: u32 = 1080, width: u32 = 1920 }) { + // logic... +} +``` + +A coercion could then allow you to write: + +```rust +open_window({ .. }); +``` + +This could be interpreted as `open_window({ RangeFull })`, see the previous +section for a discussion... alternatively `open_window(_)` could be permitted +instead for general value inference where `_` is a placeholder expression +similar to `_` as a type expression placeholder +(i.e. a fresh and unconstrained unification variable). + +If you wanted to override a default, you would write: + +```rust +open_window({ height: 720, }); +``` + +Note that the syntax used to give fields in structural records defaults belongs +to the type grammar; in other words, the following would be legal: + +```rust +type RGB = { red: u8 = 0, green: u8 = 0, blue: u8 = 0 }; + +let color: RGB = { red: 255, }; +``` + +As structural records are not yet in the language, +figuring out designs for how to extend this RFC to them is left +as possible work for the future. + + +## Integration with struct literal type inference + +Yet another common requested feature is the introduction of struct literal type +inference in the form of elision of the name of an ADT literal when it can be +gleaned from context. This has sometimes been proposed as an alternative or +complementary to structural records. This would allow people to write +`foo(_ { bar: 42 })` where the function argument type is inferred from the `foo` +definition. struct literal type inference with default struct fields would also +allow people to write APIs that "feel" like named function arguments when +calling them, although not when defining them. + +```rust +struct Config { + height: u32 = 1080, + width: u32 = 1920, +} +fn open_window(config: Config) { + // logic... +} + +open_window(_ { width: 800, .. }); +``` + +## Accessing default values from the type + +If one were to conceptualize default field values in the following way: + +```rust +struct Config { + height: u32 = Self::HEIGHT, + width: u32 = Self::WIDTH, +} + +impl Config { + const HEIGHT: u32 = 1080, + const WIDTH: u32 = 1920, +} +``` + +It would follow that one should be able to access the value of these defaults +*without* constructing `Config`, by writing `Config::HEIGHT`. I do not believe +this should be done or advanced, but there's nothing in this RFC that precludes +*some* mechanism to access these values in the future. With the RFC as written, +these values can be accessed by instantiating `Config { .. }.height`, as long +as `height` is visible in the current scope. + +## Non-const values + +Although there are strong reasons to restrict default values only to const +values, it would be possible to allow non-const values as well, potentially +allowed but linted against. Expanding the kind of values that can be accepted +can be expanded in the future. \ No newline at end of file From 7d3823ba43a4e2bcba6204ca4de1a5f6e9e0da6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Fri, 23 Aug 2024 17:49:37 +0000 Subject: [PATCH 02/37] Remove example of using the functional update syntax and reword summary --- text/0000-default-field-values.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/text/0000-default-field-values.md b/text/0000-default-field-values.md index 298458f11ed..db6b4184928 100644 --- a/text/0000-default-field-values.md +++ b/text/0000-default-field-values.md @@ -18,18 +18,23 @@ struct Pet { } ``` -These can then be used in the following way with the Functional Record Update syntax, with no value: +[FUS]: https://doc.rust-lang.org/reference/expressions/struct-expr.html#functional-update-syntax + +These can then be used in the following way with the existing [functional update +syntax][FUS], but without a "base expression" after the `..`: ```rust // Pet { name: Some(""), age: 42 } let _ = Pet { name: Some(String::new()), .. } // Compilation error: `name` needs to be specified let _ = Pet { .. } -// Derived `Default` uses struct field defaults if present: +``` + +Derived `Default` `impl` also uses struct field defaults if present: + +```rust // Pet { name: None, age: 42 } let _ = Pet::default(); -// Pet { name: None, age: 42 } -let _ = Pet { ..Pet::default() }; ``` # Motivation From 608c47c0bddf9136f544422b062d15984baec425 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Fri, 23 Aug 2024 18:51:58 +0000 Subject: [PATCH 03/37] Change argument for non_exhaustive to not allow it at first --- text/0000-default-field-values.md | 93 +++++++++++++++++++++++++++++-- 1 file changed, 88 insertions(+), 5 deletions(-) diff --git a/text/0000-default-field-values.md b/text/0000-default-field-values.md index db6b4184928..20ca520d167 100644 --- a/text/0000-default-field-values.md +++ b/text/0000-default-field-values.md @@ -590,13 +590,22 @@ if you allow for the possibility of having fields be ignored with `..` (14): let Config { width, height, .. } = config; ``` -Because this RFC gives a way to have default field values, you can now simply -invert the pattern expression and initialize a `Config` like so (15): +This RFC restricts the use of default field values only to types that are *not* +annotated with `#[non_exhaustive]`, leaving it and the specifics of their +interaction if allowed as an open question of future concern. Supporting this +without additional compiler support could mean that the following ```rust -let config = Config { width, height, .. }; +#[non_exhaustive] +pub struct Foo; + +// another crate +let _ = Foo { .. }; // Currently forbidden ``` +Would be *allowed*, changing the meaning of this code in a way that goes against +user intention. + ## Defaults for `enum`s The ability to give fields default values is not limited to `struct`s. @@ -632,7 +641,7 @@ Furthermore, when `#[non_exhaustive]` is specified directly on an `enum`, it has no interaction with the defaults values and the ability to construct variants of said enum. However, as specified by [RFC 2008], `#[non_exhaustive]` is permitted on variants. When that occurs, the behaviour is the same as if -it had been attached to a `struct` with the same fields. +it had been attached to a `struct` with the same fields and field visibility. ### Interaction with `#[default]` @@ -1579,7 +1588,8 @@ this RFC so that constructor functions are regained if so desired. } ``` - it is possible to construct a `Config` like so: + it could be possible to construct a `Config` like so, if the construction of + types without default field values is allowed (to support semver changes): ```rust let config = Config { width: 640, height: 480, .. }; @@ -1603,6 +1613,79 @@ this RFC so that constructor functions are regained if so desired. # Future possibilities [future-possibilities]: #future-possibilities +## `#[non_exhaustive]` interactions + +This RFC doesn't allow mixing default field values and `#[non_exhaustive]` +because of the interaction with the allowance to build struct literals +that have private fields: + +```rust +#[non_exhaustive] +pub struct Foo { + bar: i32 = 42, +} + +// another crate +let _ = Foo { .. }; // Currently forbidden, but would be allowed by this RFC without the attribute +``` + +There are several options: + + - Allow `#[non_exhaustive]` but deny the hability to build a struct literal + when there are non-accessible fields with defaults + - Disallow both `#[non_exhaustive]` and building struct literals with private + fields in order to resolve the interaction some-time in the future, as + *enabling* either hability is a backwards compatible change that strictly + allows more code to work + - Have additional rules on what the interactions are, like for example allow + building struct literals with private fields *as long as* the type isn't + annotated with `#[non_exhaustive]` + - Extend `#[non_exhaustive]` with arguments in order to specify the desired + behavior + - Change the defaults of `#[non_exhaustive]` and allow for the change in + meaning of it being set + +I propose to go for the maximally restrictive version of the default field +values feature, and allow for future experimentation of which of these options +best fits the language. + +The following also needs to be specified: + +```rust +#[non_exhaustive] +pub struct Foo; + +// another crate +let _ = Foo { .. }; // Currently forbidden +``` + +## "Empty" types and types without default field values + +Under this RFC, the following code isn't specified one way or the other: + +```rust +pub struct Foo; + +let _ = Foo { .. }; // should be denied +``` + +I propose we disallow this at least initially. `..` can then *only* be used +if there is at least one default field. We might want to change this rule in +the future, but careful with how it would interact with `#[non_exhaustive]`, as +it could accidentally allow for types that are not meant to be constructed +outside of a given crate to all of a sudden be constructable. + +One alternative can be to provide an explicit opt-in attribute to allow for the +use of default field values even if the type doesn't currently have any: + +```rust +#[allow(default_field_construction)] +pub struct Foo; + +let _ = Foo { .. }; // ok +``` + + ## Tuple structs and tuple variants Although it could, this proposal does not offer a way to specify default values From 14496f35e7e5315b0109b2087c1d279e56ae3afe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Fri, 23 Aug 2024 18:54:18 +0000 Subject: [PATCH 04/37] mention future lint --- text/0000-default-field-values.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/text/0000-default-field-values.md b/text/0000-default-field-values.md index 20ca520d167..0fc550c9570 100644 --- a/text/0000-default-field-values.md +++ b/text/0000-default-field-values.md @@ -1852,4 +1852,12 @@ as `height` is visible in the current scope. Although there are strong reasons to restrict default values only to const values, it would be possible to allow non-const values as well, potentially allowed but linted against. Expanding the kind of values that can be accepted -can be expanded in the future. \ No newline at end of file +can be expanded in the future. + +## Lint against explicit `impl Default` when `#[derive(Default)]` would be ok + +As a future improvement, we could nudge implementors towards leveraging the +feature for less verbosity, but care will have to be taken in not being overly +annoying, particularly for crates that have an MSRV that would preclude them +from using this feature. This could be an edition lint, which would simplify +implementation. \ No newline at end of file From 4564ea2a0b828fb90d508750f52c56d2c298c565 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Fri, 23 Aug 2024 18:58:13 +0000 Subject: [PATCH 05/37] const patterns drawback --- text/0000-default-field-values.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/text/0000-default-field-values.md b/text/0000-default-field-values.md index 0fc550c9570..98b9c7ddde1 100644 --- a/text/0000-default-field-values.md +++ b/text/0000-default-field-values.md @@ -845,6 +845,14 @@ error: default values on `struct` fields aren't supported | ^^^^^ help: remove this unsupported default value ``` +An issue arises when considering `const` patterns. A pattern `Foo { .. }` can +match more things than just the expression `Foo { .. }`, because the pattern +matches any value of the unmentioned fields, but the expression sets them to a +particular value. This means that, with the unstable `inline_const_pat`, the arm +`const { Foo { .. } } =>` matches less than the arm `Foo { .. } =>` (assuming a +type like `struct Foo { a: i32 = 1 }`). A way to mitigate this might be to use +an alternative syntax, like `...` or `..kw#default`. + # Rationale and alternatives [rationale-and-alternatives]: #rationale-and-alternatives From ba93bfc988e74a59ab9da766e20930aeef6b1bad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Fri, 23 Aug 2024 19:01:18 +0000 Subject: [PATCH 06/37] changes to privacy rules --- text/0000-default-field-values.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/text/0000-default-field-values.md b/text/0000-default-field-values.md index 98b9c7ddde1..1555d773383 100644 --- a/text/0000-default-field-values.md +++ b/text/0000-default-field-values.md @@ -971,15 +971,19 @@ remains at all times private to `alpha`. Therefore, privacy, and by extension soundness, is preserved. If a user wishes to keep other modules from constructing a `Foo` with -`Foo { .. }` they can simply add, or keep, one private field without a default. -Situations where this can be important include those where `Foo` is some token -for some resource and where fabricating a `Foo` may prove dangerous or worse -unsound. This is however no different than carelessly adding `#[derive(Default)]`. +`Foo { .. }` they can simply add, or keep, one private field without a default +or add `#[non_exhaustive]`, as mising these two features is not allowed under +this RFC. Situations where this can be important include those where `Foo` is +some token for some resource and where fabricating a `Foo` may prove dangerous +or worse unsound. This is however no different than carelessly adding +`#[derive(Default)]`. The feature *can* be stabilized *without* the privacy changes, turning the above snippet into invalid code, in order to expedite the acceptance of this change, leaving us to revisit this question at a later time once we get further real -world experience. +world experience, particularly regarding questions around allowing the use of +this syntax with types that have *no* default fields, including for ZST, as well +as the open questions around interactions with `#[non_exhaustive]`. ## On `const` contexts From 607271beb01cf15b7a31c7169d296e2caa8e58f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Fri, 23 Aug 2024 19:02:41 +0000 Subject: [PATCH 07/37] add suggested paragraph --- text/0000-default-field-values.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/text/0000-default-field-values.md b/text/0000-default-field-values.md index 1555d773383..aedcab296c3 100644 --- a/text/0000-default-field-values.md +++ b/text/0000-default-field-values.md @@ -1136,6 +1136,12 @@ Among the drawbacks are: + By writing `Foo { .. }`, you have there is explicit indication that default values are being used; this enhances local reasoning further. +This RFC requires the `..` to get defaulted fields because it wants to continue +to allow the workflow of intentionally *not* including `..` in the struct +literal expression so that when a user adds a field they get compilation errors +on every use -- just like is currently possible in patterns by not including +`..` in the struct pattern. + ## Named function arguments with default values A frequently requested feature is named function arguments. Today, the way to From 89e466d8c2d1651d76da2569ee8ace14deaa16f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Fri, 23 Aug 2024 19:08:15 +0000 Subject: [PATCH 08/37] Add review comments, talk about _ in struct literals --- text/0000-default-field-values.md | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/text/0000-default-field-values.md b/text/0000-default-field-values.md index aedcab296c3..b08cd17aed3 100644 --- a/text/0000-default-field-values.md +++ b/text/0000-default-field-values.md @@ -732,6 +732,7 @@ the rule `StructExprFieldsAndBase` is extended with: ```rust StructExprFieldsAndBase =| FieldsAndDefault:{ fields:StructExprField+ % "," "," ".." }; +StructExprFieldsAndBase =| Default:{ ".." } ``` ### Static semantics @@ -748,7 +749,7 @@ all the following rules apply when type-checking: 1. The expression `def` must be a constant expression. -2. The expression `def` must unify with the type `ty`. +2. The expression `def` must coerces with the type `ty`. When lints check attributes such as `#[allow(lint_name)]` are placed on a `RecordField`, it also applies to `def` if it exists. @@ -1437,7 +1438,7 @@ enum Foo { There a few aspects to note: -1. The signal to noise ratio is high as compared to the notation in this RFC. +1. The signal to noise ratio is low as compared to the notation in this RFC. Substantial of syntactic overhead is accumulated to specify defaults. 2. Expressions need to be wrapped in strings, i.e. `value="2 * (1 << 20)"`. @@ -1703,6 +1704,24 @@ pub struct Foo; let _ = Foo { .. }; // ok ``` +## Use of `_` on struct literals + +On patterns, one can currently use `field: _` to explicitly ignore a single +named field, in order to force a compilation error at the pattern use place +if a field is explicitly added to the type. One could envision a desire to +allow for the use of the same syntax during construction, as an explicit +expression to set a given default, but still fail to compile if a field has +been added to the type: + +```rust +struct Foo { + bar: i32 = 42, +} + +let _ = Foo { + bar: _, +}; +``` ## Tuple structs and tuple variants From 37bb518a6a2d0ee31b2c4f565b512399531decb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Fri, 23 Aug 2024 19:14:06 +0000 Subject: [PATCH 09/37] mention drawback of complexity of choice --- text/0000-default-field-values.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/text/0000-default-field-values.md b/text/0000-default-field-values.md index b08cd17aed3..3c678044288 100644 --- a/text/0000-default-field-values.md +++ b/text/0000-default-field-values.md @@ -831,6 +831,34 @@ In particular, the syntax `Foo { .. }` mirrors the identical and already existing pattern syntax. This makes the addition of `Foo { .. }` at worst low-cost and potentially cost-free. +It is true that there are cases where `Foo { ..Default::default() }` will be +allowed where `Foo { .. }` won't be, and vice-versa. + +This new syntax is more ergonomic to use, but it requires specifying a default +value for every field which can be much less ergonomic than using +`#[derive(Default)]` on your type. The following two are almost equivalent, and +the more fields there are, the more the verbosity is increased: + +```rust +#[derive(Default)] +struct S { + foo: Option, + bar: Option, +} +``` + +```rust +struct S { + foo: Option = None, + bar: Option = None, +} +``` + +This can become relevant when an API author wants to push users towards the new +syntax because `..` is shorter than `..Default::default()`, or when some fields +with types that `impl Default` are optional, but `#[derive(Default)]` can't be +used because some fields are mandatory. + The main complexity comes instead from introducing `field: Type = expr`. However, as seen in the [prior-art], there are several widely-used languages that have a notion of field / property / instance-variable defaults. From 9d528a3519e98ef74d48b4b5986a3315a001874a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Fri, 23 Aug 2024 19:16:31 +0000 Subject: [PATCH 10/37] remove PhantomData argument as it is no longer necessary --- text/0000-default-field-values.md | 78 +------------------------------ 1 file changed, 1 insertion(+), 77 deletions(-) diff --git a/text/0000-default-field-values.md b/text/0000-default-field-values.md index 3c678044288..93a66d04153 100644 --- a/text/0000-default-field-values.md +++ b/text/0000-default-field-values.md @@ -232,82 +232,6 @@ fn main() { The builder pattern is quite common in the Rust ecosystem, but as shown above its need is greatly reduced with `struct` field defaults. -### `PhantomData` ergonomics - -[nomicon]: https://doc.rust-lang.org/nightly/nomicon/phantom-data.html -[PhantomData]: https://doc.rust-lang.org/stable/std/marker/struct.PhantomData.html - -Say that you want to define our own `Vec` type. As the [nomicon] says, -you need to use [PhantomData] to make sure that drop-checking is sound like so: - -```rust -use std::marker::PhantomData; - -struct Vec { - data: *const T, // *const for variance! - len: usize, - cap: usize, - _marker: PhantomData, -} -``` - -You then go on to define some operations for `Vec`: - -```rust -impl Vec { - pub fn new() -> Self { - Self { - // other fields... - _marker: PhantomData, - } - } - - pub fn with_capacity(capacity: usize) -> Self { - Self { - // other fields... - _marker: PhantomData, - } - } -} -``` - -Here, `_marker: PhantomData` is just provided to satisfy the compiler. -`PhantomData` carries no useful information since it is a ZST. -Therefore, the result of having to add a field of type `PhantomData` -is a regression to ergonomics throughout. With default values you can -improve the situation slightly and provide the value in a single place: - -```rust -use std::marker::PhantomData; - -struct Vec { - data: *const T, // *const for variance! - len: usize, - cap: usize, - _marker: PhantomData = PhantomData, -} -``` - -You then go on to define some operations for `Vec`: - -```rust -impl Vec { - pub fn new() -> Self { - Self { - // other fields... - .. - } - } - - pub fn with_capacity(capacity: usize) -> Self { - Self { - // other fields... - .. - } - } -} -``` - ## `#[derive(Default)]` in more cases The `#[derive(..)]` ("custom derive") mechanism works by defining procedural @@ -473,7 +397,7 @@ mix and match. Given the definition of `LaunchCommand` from the [motivation] (7) ```rust struct LaunchCommand { cmd: String, - args: Vec = vec![], + args: Vec = Vec::new(), some_special_setting: Option = None, setting_most_people_will_ignore: Option = None, } From cb47c9b42524755730682663cbff2a20be559b44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Fri, 23 Aug 2024 19:24:52 +0000 Subject: [PATCH 11/37] link to RFC-0736 --- text/0000-default-field-values.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/text/0000-default-field-values.md b/text/0000-default-field-values.md index 93a66d04153..838a2789d87 100644 --- a/text/0000-default-field-values.md +++ b/text/0000-default-field-values.md @@ -895,9 +895,15 @@ variable. ## On privacy +[RFC-0736]: https://github.com/rust-lang/rfcs/blob/master/text/0736-privacy-respecting-fru.md + This RFC specifies that when field defaults are specified, the desugaring semantics of `Foo { .. }` spans `field: default_value` with the context in -which the type is defined as opposed to where `Foo { .. }` occurs. +which the type is defined as opposed to where `Foo { .. }` occurs, in the same +way that the Functional Record Update syntax worked *before* [RFC-0736], where +we previously allowed for the construction of a value with private fields with +values from a base expression. + In other words, the following is valid: ```rust @@ -931,8 +937,8 @@ some token for some resource and where fabricating a `Foo` may prove dangerous or worse unsound. This is however no different than carelessly adding `#[derive(Default)]`. -The feature *can* be stabilized *without* the privacy changes, turning the above -snippet into invalid code, in order to expedite the acceptance of this change, +**The feature *can* be stabilized *without* the privacy changes, turning the above +snippet into invalid code, in order to expedite the acceptance of this change**, leaving us to revisit this question at a later time once we get further real world experience, particularly regarding questions around allowing the use of this syntax with types that have *no* default fields, including for ZST, as well @@ -1578,9 +1584,6 @@ this RFC so that constructor functions are regained if so desired. omit `#[non_exhaustive]` from their type and instead add a private defaulted field that has a ZST. - Resolving this question may be deferred to stabilization of either - `#[non_exhaustive]` or field defaults, whichever comes last. - # Future possibilities [future-possibilities]: #future-possibilities From 986244169741f7b97f21ad1042a8f9fc39c643a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Fri, 23 Aug 2024 19:25:42 +0000 Subject: [PATCH 12/37] remove dynamic semantics section --- text/0000-default-field-values.md | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/text/0000-default-field-values.md b/text/0000-default-field-values.md index 838a2789d87..b896ca63b2c 100644 --- a/text/0000-default-field-values.md +++ b/text/0000-default-field-values.md @@ -717,20 +717,6 @@ are also constant expressions, then `τ` is a constant expression. **Proof:** `x` is a special case of `τ` with all fields according to the default values. Since there are no explicitly provided fields, by Lemma 1 it holds. -### Dynamic semantics - -Given a well-formed expression `τ` according to the aforementioned form, -the operational semantics is the same as the semantics of an expression -`path { new_fields }` with `new_fields = fields ∪ default_fields` where: - -+ `∪` computes the left-biased union where equal elements are determined - solely on equality of field names. - -+ `fields` are the set of explicitly given `field: expr` pairs. - -+ `default_fields` are the fields in `τ`'s data-type or variant which - have provided default values. - ## `#[derive(Default)]` When generating an implementation of `Default` for a `struct` named `$s` on From ed9266908f34fd1dab444305e97bbe6d48edf472 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Fri, 23 Aug 2024 19:26:47 +0000 Subject: [PATCH 13/37] incorporate review feedback --- text/0000-default-field-values.md | 38 ++----------------------------- 1 file changed, 2 insertions(+), 36 deletions(-) diff --git a/text/0000-default-field-values.md b/text/0000-default-field-values.md index b896ca63b2c..a0e06374c69 100644 --- a/text/0000-default-field-values.md +++ b/text/0000-default-field-values.md @@ -680,42 +680,8 @@ When lints check attributes such as `#[allow(lint_name)]` are placed on a #### Initialization expressions -Given an expression `τ` of the form (e.g. `Foo { x: 1, .. }`): - -```rust -path:Path "{" attrs:InnerAttr* fields:StructExprField+ % "," "," ".." "}" -``` - -all the following rules apply when type-checking: - -1. All `fields` in `τ` mentioned must exist in the `struct` or `enum` variant - referenced by `path`. No field in `fields` may be mentioned twice. - -2. For each `field: expr` pair in `fields` of `τ`, - `expr` must unify with the type of `field` in the `struct` - or `enum` variant referenced by `path`. - -3. For each `field: expression` pair in `fields` of `τ`, - `field` must be visible in the context which `τ` occurs in. - -4. For each field `f` in `τ` *not* mentioned in `fields`, a default value must - exist for `f` in the `struct` or `enum` variant referenced by `path`. - ---------------------- - -**Lemma 1:** If `τ` is of a type `σ` where `σ: Sync`, and if all `field: expr` -has an `expr` component which is a valid constant expression, then `τ` is -also a valid constant expression. - -**Proof:** It trivially holds that if all `expr`s in `τ` are -constant expressions and all default values for fields omitted -are also constant expressions, then `τ` is a constant expression. - -**Lemma 2:** If an expression `x` of form `Path { .. }` is of a type `σ` where -`σ: Sync`, then `x` is a constant expression. - -**Proof:** `x` is a special case of `τ` with all fields according to the default -values. Since there are no explicitly provided fields, by Lemma 1 it holds. +`Path { fields, .. }` is `const` since the defaulted fields are initialized +from constants. ## `#[derive(Default)]` From 6aa312d82874e91d944e8477473106324c7ad63a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Fri, 23 Aug 2024 19:33:21 +0000 Subject: [PATCH 14/37] fix typo --- text/0000-default-field-values.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/0000-default-field-values.md b/text/0000-default-field-values.md index a0e06374c69..ee292564b8f 100644 --- a/text/0000-default-field-values.md +++ b/text/0000-default-field-values.md @@ -688,8 +688,8 @@ from constants. When generating an implementation of `Default` for a `struct` named `$s` on which `#[derive(Default)]` has been attached, the compiler will omit all fields which have default values provided in the `struct`. The the associated function -`default` shall then be defined as (where `$f_i` denotes a vector of the fields -of `$s`): +`default` shall then be defined as (where `$f_i` denotes the `i`-th field of +`$s`): ```rust fn default() -> Self { From 76e9a4c5fc2406219be0a262d6413bd48ea83ea8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Fri, 23 Aug 2024 19:34:05 +0000 Subject: [PATCH 15/37] use better structopt example --- text/0000-default-field-values.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-default-field-values.md b/text/0000-default-field-values.md index ee292564b8f..185653367f2 100644 --- a/text/0000-default-field-values.md +++ b/text/0000-default-field-values.md @@ -282,7 +282,7 @@ Another example is the `structopt` crate with which you can write: #[structopt(name = "example", about = "An example of StructOpt usage.")] struct Opt { /// Set speed - #[structopt(short = "s", long = "speed", default_value = "42")] + #[structopt(short = "s", long = "speed", default_value_t = 42)] speed: f64, ... } From b34f44c4a37d6a0729b9b215595d97ba920e1fed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Sat, 24 Aug 2024 17:52:34 +0000 Subject: [PATCH 16/37] some tmandry comments --- text/0000-default-field-values.md | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/text/0000-default-field-values.md b/text/0000-default-field-values.md index 185653367f2..e25ffac0d10 100644 --- a/text/0000-default-field-values.md +++ b/text/0000-default-field-values.md @@ -673,7 +673,7 @@ all the following rules apply when type-checking: 1. The expression `def` must be a constant expression. -2. The expression `def` must coerces with the type `ty`. +2. The expression `def` must coerce to the type `ty`. When lints check attributes such as `#[allow(lint_name)]` are placed on a `RecordField`, it also applies to `def` if it exists. @@ -1557,11 +1557,11 @@ let _ = Foo { .. }; // Currently forbidden, but would be allowed by this RFC wit There are several options: - - Allow `#[non_exhaustive]` but deny the hability to build a struct literal + - Allow `#[non_exhaustive]` but deny the ability to build a struct literal when there are non-accessible fields with defaults - Disallow both `#[non_exhaustive]` and building struct literals with private fields in order to resolve the interaction some-time in the future, as - *enabling* either hability is a backwards compatible change that strictly + *enabling* either ability is a backwards compatible change that strictly allows more code to work - Have additional rules on what the interactions are, like for example allow building struct literals with private fields *as long as* the type isn't @@ -1664,15 +1664,10 @@ there are some tricky design choices; in particular: + The syntax `Alpha(..)` as an expression already has a meaning. Namely, it is sugar for `Alpha(RangeFull)`. Thus unfortunately, this syntax cannot be used to mean `Alpha(42, true)`. - In some future edition, the syntax `Alpha(...)` (three dots) - could be used for filling in defaults. This would ostensibly entail + In newer editions, the syntax `Alpha(...)` (three dots) + can be used for filling in defaults. This would ostensibly entail adding the pattern syntax `Alpha(...)` as well. - Until a syntax such as `Alpha(...)` is made available, - tuple structs could be constructed with `Alpha { 0: 43 }`. - However, that is not particularly ergonomic and that diminishes the value - of having field defaults for tuple structs. - For these reasons, default values for positional fields are not included in this RFC and are instead left as a possible future extension. From 351f897cda02d4cd249e336a68f66fb4a6fbc26a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Sun, 25 Aug 2024 19:07:39 +0000 Subject: [PATCH 17/37] mention enum variant derive default issue --- text/0000-default-field-values.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/text/0000-default-field-values.md b/text/0000-default-field-values.md index e25ffac0d10..72d09f1f6e4 100644 --- a/text/0000-default-field-values.md +++ b/text/0000-default-field-values.md @@ -1505,6 +1505,7 @@ this RFC so that constructor functions are regained if so desired. # Unresolved questions [unresolved-questions]: #unresolved-questions +## `#[non_exhaustive]` 1. What is the right interaction wrt. `#[non_exhaustive]`? In particular, if given the following definition: @@ -1536,6 +1537,16 @@ this RFC so that constructor functions are regained if so desired. omit `#[non_exhaustive]` from their type and instead add a private defaulted field that has a ZST. +## `enum` variants + +[RFC-3683]: https://github.com/rust-lang/rfcs/pull/3683 + +Currently `#[derive(Default)]` only supports unit `enum` variants. In this RFC +we propose supporting `..` on struct `enum` variants. It would be nice to keep +the symmetry with `struct`s and support `#[derive(Default)]` on them, but it is +not absolutely necessary. [RFC-3683] proposes that support. These two features +are technically orthogonal, but work well together. + # Future possibilities [future-possibilities]: #future-possibilities From 94c8d48f888c379673b7b3935898bc9d436c19eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Sun, 25 Aug 2024 19:40:01 +0000 Subject: [PATCH 18/37] move privacy behavior changes to future possibilities --- text/0000-default-field-values.md | 171 ++++++++++++++++-------------- 1 file changed, 92 insertions(+), 79 deletions(-) diff --git a/text/0000-default-field-values.md b/text/0000-default-field-values.md index 72d09f1f6e4..4df1cf9b2c6 100644 --- a/text/0000-default-field-values.md +++ b/text/0000-default-field-values.md @@ -49,8 +49,7 @@ let _ = Pet::default(); Rust allows you to create an instance of a `struct` using the struct literal syntax `Foo { bar: expr, baz: expr }`. To do so, all fields in the `struct` must be assigned a value. This makes it inconvenient to create large `struct`s -whose fields usually receive the same values. Struct literals also cannot be -used to initialize `struct`s with fields which are inaccessible due to privacy. +whose fields usually receive the same values. *[Functional record updates (FRU)][FRU]* can reduce noise when a `struct` derives `Default`, but are also invalid when the `struct` has inaccessible fields and do not allow the creation of an `impl` where *some* fields are @@ -456,32 +455,11 @@ deterministic compilation. ## Privacy interactions -In examples thus far, initialization literal expressions `Foo { bar, .. }` have -been used in the same module as where the constructed type was defined in. - -Let's now consider a scenario where this is not the case (11): - -```rust -pub mod foo { - pub struct Alpha { - beta: u8 = 42, - gamma: bool = true, - } -} - -mod bar { - fn baz() { - let x = Alpha { .. }; - } -} -``` - -Despite `foo::bar` being in a different module than `foo::Alpha` and despite -`beta` and `gamma` being private to `foo::bar`, a Rust compiler will accept -the above snippet. It is legal because when `Alpha { .. }` expands to -`Alpha { beta: 42, gamma: true }`, the fields `beta` and `gamma` are considered -in the context of `foo::Alpha`'s *definition site* rather than `bar::baz`'s -definition site. +The same privacy interactions that the struct update syntax has when a base is +present are still at place under this RFC: if a type can't be constructed from +another base expression due to private fields, then it can't be constructed from +field defaults either. See [Future Possibilities][future-privacy] for additional +context. ## `#[non_exhaustive]` interactions @@ -845,57 +823,6 @@ unimportant going on. For patterns, `_` matches everything and doesn't give access to the value; for types, the placeholder is just an unbounded inference variable. -## On privacy - -[RFC-0736]: https://github.com/rust-lang/rfcs/blob/master/text/0736-privacy-respecting-fru.md - -This RFC specifies that when field defaults are specified, the desugaring -semantics of `Foo { .. }` spans `field: default_value` with the context in -which the type is defined as opposed to where `Foo { .. }` occurs, in the same -way that the Functional Record Update syntax worked *before* [RFC-0736], where -we previously allowed for the construction of a value with private fields with -values from a base expression. - -In other words, the following is valid: - -```rust -pub mod alpha { - pub struct Foo { - field: u8 = 42, - } -} - -use alpha::Foo; - -mod beta { - // `field` isn't visible here. - fn bar() -> Foo { - Foo { .. } // OK! - } -} -``` - -By permitting the above snippet, you are able to construct a default value -for a type more ergonomically with `Foo { .. }`. Since it isn't possible for -functions in `beta` to access `field`'s value, the value `42` or any other -remains at all times private to `alpha`. Therefore, privacy, and by extension -soundness, is preserved. - -If a user wishes to keep other modules from constructing a `Foo` with -`Foo { .. }` they can simply add, or keep, one private field without a default -or add `#[non_exhaustive]`, as mising these two features is not allowed under -this RFC. Situations where this can be important include those where `Foo` is -some token for some resource and where fabricating a `Foo` may prove dangerous -or worse unsound. This is however no different than carelessly adding -`#[derive(Default)]`. - -**The feature *can* be stabilized *without* the privacy changes, turning the above -snippet into invalid code, in order to expedite the acceptance of this change**, -leaving us to revisit this question at a later time once we get further real -world experience, particularly regarding questions around allowing the use of -this syntax with types that have *no* default fields, including for ZST, as well -as the open questions around interactions with `#[non_exhaustive]`. - ## On `const` contexts To recap, the expression a default value is computed with must be constant one. @@ -1596,6 +1523,92 @@ pub struct Foo; let _ = Foo { .. }; // Currently forbidden ``` +## Privacy: building `struct`s with private defaulted fields + +[privacy]: #future-privacy + +[RFC-0736]: https://github.com/rust-lang/rfcs/blob/master/text/0736-privacy-respecting-fru.md + + +In this RFC we do not propose any changes to the normal visibility rules: +constructing a `struct` with default fields requires those fields to be visible +in that scope. + +Let's consider a scenario where this comes into play: + +```rust +pub mod foo { + pub struct Alpha { + beta: u8 = 42, + gamma: bool = true, + } +} + +mod bar { + fn baz() { + let x = Alpha { .. }; + } +} +``` + +Despite `foo::bar` being in a different module than `foo::Alpha` and despite +`beta` and `gamma` being private to `foo::bar`, a Rust compiler could accept +the above snippet. It would be legal because when `Alpha { .. }` expands to +`Alpha { beta: 42, gamma: true }`, the fields `beta` and `gamma` can be +considered in the context of `foo::Alpha`'s *definition site* rather than +`bar::baz`'s definition site. + +By permitting the above snippet, you are able to construct a default value +for a type more ergonomically with `Foo { .. }`. Since it isn't possible for +functions in `beta` to access `field`'s value, the value `42` or any other +remains at all times private to `alpha`. Therefore, privacy, and by extension +soundness, is preserved. + +This used to be the behavior the Functional Record Update syntax had *before* +[RFC-0736], where we previously allowed for the construction of a value with +private fields with values from a base expression. + +If a user wishes to keep other modules from constructing a `Foo` with +`Foo { .. }` they can simply add, or keep, one private field without a default +or add `#[non_exhaustive]`, as mising these two features is not allowed under +this RFC. Situations where this can be important include those where `Foo` is +some token for some resource and where fabricating a `Foo` may prove dangerous +or worse unsound. This is however no different than carelessly adding +`#[derive(Default)]`. + +Changing this behavior after stabilization of this RFC does present a potential +foot-gun: if an API author relies on the privacy of a defaulted field to make a +type unconstructable outside of its defining crate, then this change would cause +the API to no longer be correct, needing the addition of a non-defaulted private +field to keep its prior behavior. If we were to make this change, we could lint +about the situation when all default values are private, which would be silenced +by adding another non-defaulted private field. + +Another alternative would be to allow this new behavior in an opt in manner, +such as an attribute or item modifier: + +```rust +pub mod foo { + #[allow_private_defaults] + pub struct Alpha { + beta: u8 = 42, + gamma: bool = true, + } +} +``` + +```rust +pub mod foo { + pub(default) struct Alpha { + beta: u8 = 42, + gamma: bool = true, + } +} +``` + +Additionally, the interaction between this privacy behavior and +`#[non_exhaustive]` is fraught and requires additional discussion. + ## "Empty" types and types without default field values Under this RFC, the following code isn't specified one way or the other: From f07e3a869114539e65f9f9b0c5db64e7e497105d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Sun, 25 Aug 2024 19:43:03 +0000 Subject: [PATCH 19/37] add xlink --- text/0000-default-field-values.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/text/0000-default-field-values.md b/text/0000-default-field-values.md index 4df1cf9b2c6..5167186c3cd 100644 --- a/text/0000-default-field-values.md +++ b/text/0000-default-field-values.md @@ -1812,7 +1812,9 @@ as `height` is visible in the current scope. ## Non-const values -Although there are strong reasons to restrict default values only to const +[strong reasons]: #on-const-contexts + +Although there are [strong reasons] to restrict default values only to const values, it would be possible to allow non-const values as well, potentially allowed but linted against. Expanding the kind of values that can be accepted can be expanded in the future. From ae62f32e77c94a84cda4733815946dcc2901e84b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Sun, 25 Aug 2024 19:46:28 +0000 Subject: [PATCH 20/37] mention accessing assoc-consts is allowed --- text/0000-default-field-values.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/text/0000-default-field-values.md b/text/0000-default-field-values.md index 5167186c3cd..6be6cb240a9 100644 --- a/text/0000-default-field-values.md +++ b/text/0000-default-field-values.md @@ -1810,6 +1810,21 @@ this should be done or advanced, but there's nothing in this RFC that precludes these values can be accessed by instantiating `Config { .. }.height`, as long as `height` is visible in the current scope. +Note that the *opposite* is supported, writing that code will compile, so any +API author that wants to make these `const` values on the type can (only +restriction is that `Self` isn't accepted in current `const` contexts): + +```rust +struct Config { + height: u32 = Config::HEIGHT, + width: u32 = Config::WIDTH, +} + +impl Config { + const HEIGHT: u32 = 1080, + const WIDTH: u32 = 1920, +} +``` ## Non-const values [strong reasons]: #on-const-contexts From ff6e6875b2218bc8f9bfc61b421a5f6115316660 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Sun, 25 Aug 2024 19:50:26 +0000 Subject: [PATCH 21/37] Mention _ for tuples --- text/0000-default-field-values.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/text/0000-default-field-values.md b/text/0000-default-field-values.md index 6be6cb240a9..50dcf77f920 100644 --- a/text/0000-default-field-values.md +++ b/text/0000-default-field-values.md @@ -1692,6 +1692,10 @@ there are some tricky design choices; in particular: can be used for filling in defaults. This would ostensibly entail adding the pattern syntax `Alpha(...)` as well. ++ As mentioned in the previous section, `_` could also be allowed in `struct` + literals. If so, then they would also be allowed in tuple literals, allowing + us to use the `struct` in the prior snippet with `Beta(_, true)`. + For these reasons, default values for positional fields are not included in this RFC and are instead left as a possible future extension. From aaf9b9a615e11a9b402ea02f7a25019a3eb67fee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Sun, 25 Aug 2024 19:55:39 +0000 Subject: [PATCH 22/37] link to perfect derives explanation --- text/0000-default-field-values.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/text/0000-default-field-values.md b/text/0000-default-field-values.md index 50dcf77f920..1b433397c3b 100644 --- a/text/0000-default-field-values.md +++ b/text/0000-default-field-values.md @@ -1273,6 +1273,8 @@ enum Foo { } ``` +[perfect-derives]: https://smallcultfollowing.com/babysteps/blog/2022/04/12/implied-bounds-and-perfect-derive/ + There a few aspects to note: 1. The signal to noise ratio is low as compared to the notation in this RFC. @@ -1300,10 +1302,10 @@ There a few aspects to note: 6. To its credit, the macro provides `#[derivative(Default(bound=""))]` with which you can remove unnecessary bounds as well as add needed ones. - This addresses a deficiency in the current deriving system for built-in - derive macros. However, the attribute solves an orthogonal problem. - The ability to specify default values would mean that `derivative` can - piggyback on the default value syntax due to this RFC. The mechanism for + This addresses a [deficiency in the current deriving system][perfect-derives] + for built-in derive macros. However, the attribute solves an orthogonal + problem. The ability to specify default values would mean that `derivative` + can piggyback on the default value syntax due to this RFC. The mechanism for removing or adding bounds can remain the same. Similar mechanisms could also be added to the language itself. From 280c94f985c38068e04143efab4fc6c89b67ce13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Sun, 25 Aug 2024 20:52:26 +0000 Subject: [PATCH 23/37] expand typeck rules to describe mir expansion --- text/0000-default-field-values.md | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/text/0000-default-field-values.md b/text/0000-default-field-values.md index 1b433397c3b..3e8980124f3 100644 --- a/text/0000-default-field-values.md +++ b/text/0000-default-field-values.md @@ -653,6 +653,34 @@ all the following rules apply when type-checking: 2. The expression `def` must coerce to the type `ty`. +3. Generic parameters of the current items are accessible +```rust +struct Bar { + field: usize = A, +} +``` + +4. Default const expressions are *not* evaluated at definition time, only + during instantiation. This means that the following will not fail to compile: +```rust +struct Bar { + field1: usize = panic!(), + field2: usize = 42, +} + +let _ = Bar { field1: 0, .. }; +``` + +5. The `struct`'s parameters are properly propagated, meaning the following is + possible: +```rust +struct Bar { + field: Vec = Vec::new(), +} + +let _ = Bar:: { .. }; +``` + When lints check attributes such as `#[allow(lint_name)]` are placed on a `RecordField`, it also applies to `def` if it exists. @@ -1846,4 +1874,4 @@ As a future improvement, we could nudge implementors towards leveraging the feature for less verbosity, but care will have to be taken in not being overly annoying, particularly for crates that have an MSRV that would preclude them from using this feature. This could be an edition lint, which would simplify -implementation. \ No newline at end of file +implementation. From 9a3d726b1ea82565d0b446f74e6606eaf7f0eaf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Mon, 26 Aug 2024 01:53:20 +0000 Subject: [PATCH 24/37] mention the possibility to side-step imperfect derives --- text/0000-default-field-values.md | 58 ++++++++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/text/0000-default-field-values.md b/text/0000-default-field-values.md index 3e8980124f3..cdca553d3ba 100644 --- a/text/0000-default-field-values.md +++ b/text/0000-default-field-values.md @@ -189,7 +189,7 @@ impl FooBuilder { unsafe { std::mem::transmute(self) } } } -// If any field is optional, +// If any field is optional, impl FooBuilder { fn build(self) -> Foo { // can only be called if all fields have been set Foo { @@ -257,6 +257,62 @@ addition of a single field, expands the code written by the `struct` author from a single `derive` line to a whole `Default` `impl`, which becomes more verbose linearly with the number of fields. +### Imperfect derives + +One thing to notice, is that taking default values into consideration during the +desugaring of `#[derive(Default)]` would allow to side-step the issue of our +lack of [perfect derives], by making the desugaring syntactically check which +type parameters correspond to fields that don't have a default field, as in the +expansion they will use the default value instead of `Default::default()`. By +doing this a user can side-step the introduction of unnecessary bounds by +specifying a default value of the same return value of `Default::default()`: + +```rust +#[derive(Default)] +struct Foo { + bar: Option, +} +``` + +previously expands to: + +```rust +struct Foo { + bar: Option, +} +impl Default for Foo { + fn default() -> Foo { + Foo { + bar: Default::default(), + } + } +} +``` + +but we can make the following: + +```rust +#[derive(Default)] +struct Foo { + bar: Option = None, +} +``` + +expand to: + +```rust +struct Foo { + bar: Option, +} +impl Default for Foo { + fn default() -> Foo { + Foo { + bar: None, + } + } +} +``` + ## Usage by other `#[derive(..)]` macros [`serde`]: https://serde.rs/attributes.html From 013a03cc6aa88b23b5e503e6ba705bbe1012d423 Mon Sep 17 00:00:00 2001 From: Esteban Kuber Date: Tue, 27 Aug 2024 13:25:06 -0700 Subject: [PATCH 25/37] Update 0000-default-field-values.md Tweak `pub(default)` syntax --- text/0000-default-field-values.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/text/0000-default-field-values.md b/text/0000-default-field-values.md index cdca553d3ba..8bf7bbce1fb 100644 --- a/text/0000-default-field-values.md +++ b/text/0000-default-field-values.md @@ -1685,9 +1685,9 @@ pub mod foo { ```rust pub mod foo { - pub(default) struct Alpha { - beta: u8 = 42, - gamma: bool = true, + struct Alpha { + pub(default) beta: u8 = 42, + pub(default) gamma: bool = true, } } ``` From 121a3c07b36a0ea962741b6f3c21e47e26e2261a Mon Sep 17 00:00:00 2001 From: Esteban Kuber Date: Wed, 28 Aug 2024 12:45:33 -0700 Subject: [PATCH 26/37] Update 0000-default-field-values.md mention that `Default` `impl`s are not `~const` today --- text/0000-default-field-values.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/text/0000-default-field-values.md b/text/0000-default-field-values.md index 8bf7bbce1fb..bb450a58dce 100644 --- a/text/0000-default-field-values.md +++ b/text/0000-default-field-values.md @@ -1924,6 +1924,9 @@ values, it would be possible to allow non-const values as well, potentially allowed but linted against. Expanding the kind of values that can be accepted can be expanded in the future. +Of note, `Default` implementations are not currently `~const`, but that is +something to be addressed by making them `~const` when suitable instead. + ## Lint against explicit `impl Default` when `#[derive(Default)]` would be ok As a future improvement, we could nudge implementors towards leveraging the From 76b4016f99c9e5aa0f4ee278ed199c5a43619a0d Mon Sep 17 00:00:00 2001 From: Esteban Kuber Date: Wed, 28 Aug 2024 14:11:46 -0700 Subject: [PATCH 27/37] Mention possibility of linting bad const default values Ment --- text/0000-default-field-values.md | 40 +++++++++++++++++-------------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/text/0000-default-field-values.md b/text/0000-default-field-values.md index bb450a58dce..32e8637ecc5 100644 --- a/text/0000-default-field-values.md +++ b/text/0000-default-field-values.md @@ -710,32 +710,36 @@ all the following rules apply when type-checking: 2. The expression `def` must coerce to the type `ty`. 3. Generic parameters of the current items are accessible -```rust -struct Bar { - field: usize = A, -} -``` + ```rust + struct Bar { + field: usize = A, + } + ``` 4. Default const expressions are *not* evaluated at definition time, only during instantiation. This means that the following will not fail to compile: -```rust -struct Bar { - field1: usize = panic!(), - field2: usize = 42, -} + ```rust + struct Bar { + field1: usize = panic!(), + field2: usize = 42, + } -let _ = Bar { field1: 0, .. }; -``` + let _ = Bar { field1: 0, .. }; + ``` + Having said that, it can be possible to proactivelly attempt to evaluate the + default values and emit a lint in a case where the expression is assured to always + fail (which would only be possible for expressions that do not reference `const` + parameters). 5. The `struct`'s parameters are properly propagated, meaning the following is possible: -```rust -struct Bar { - field: Vec = Vec::new(), -} + ```rust + struct Bar { + field: Vec = Vec::new(), + } -let _ = Bar:: { .. }; -``` + let _ = Bar:: { .. }; + ``` When lints check attributes such as `#[allow(lint_name)]` are placed on a `RecordField`, it also applies to `def` if it exists. From 2bf1c68958ce7f0a1fe6b81de77922b774ea7ca3 Mon Sep 17 00:00:00 2001 From: Esteban Kuber Date: Wed, 28 Aug 2024 14:34:30 -0700 Subject: [PATCH 28/37] Tweak wording of non_exhaustive subtleties --- text/0000-default-field-values.md | 47 +++++++++++++++++++++++++++---- 1 file changed, 42 insertions(+), 5 deletions(-) diff --git a/text/0000-default-field-values.md b/text/0000-default-field-values.md index 32e8637ecc5..7a7a8b71680 100644 --- a/text/0000-default-field-values.md +++ b/text/0000-default-field-values.md @@ -564,6 +564,42 @@ let _ = Foo { .. }; // Currently forbidden Would be *allowed*, changing the meaning of this code in a way that goes against user intention. +Some alternatives present for the case mentioned above can be: + +- Add a private non-defaulted field: + ```rust + #[non_exhaustive] + pub struct Config { + pub width: u16 = 640, + pub height: u16 = 480, + __priv: PhantomData<()> + } + ``` + which disallows the following + ```rust + let _ = Config { .. }; + let _ = Config { width: 800, height: 600, .. }; + ``` + at the cost of forcing the API-internal construction of `Config` to specify `__priv` + everywhere. +- If defaulting private fields is allowed outside of the current crate, or that behavior + can be explicitly set by the user, then the following: + ```rust + #[non_exhaustive] + pub struct Config { + pub width: u16 = 640, + pub height: u16 = 480, + __priv: PhantomData<()> = PhantomData, + } + ``` + still disallows the following + ```rust + let _ = Config { .. }; + let _ = Config { width: 800, height: 600, .. }; + ``` + while also allowing precisely that syntax within the API-internal constructions of + `Config`. + ## Defaults for `enum`s The ability to give fields default values is not limited to `struct`s. @@ -1552,7 +1588,9 @@ this RFC so that constructor functions are regained if so desired. As an alternative, users who desire the semantics described above can omit `#[non_exhaustive]` from their type and instead add a private - defaulted field that has a ZST. + defaulted field that has a ZST, if the construction of structs with + private fields is allowed. If they are not, then the attribute is still + relevant and needed to control the accepted code to force `..`. ## `enum` variants @@ -1619,7 +1657,6 @@ let _ = Foo { .. }; // Currently forbidden [RFC-0736]: https://github.com/rust-lang/rfcs/blob/master/text/0736-privacy-respecting-fru.md - In this RFC we do not propose any changes to the normal visibility rules: constructing a `struct` with default fields requires those fields to be visible in that scope. @@ -1659,8 +1696,8 @@ This used to be the behavior the Functional Record Update syntax had *before* private fields with values from a base expression. If a user wishes to keep other modules from constructing a `Foo` with -`Foo { .. }` they can simply add, or keep, one private field without a default -or add `#[non_exhaustive]`, as mising these two features is not allowed under +`Foo { .. }` they can add, or keep, one private field without a default, or add +(for now) `#[non_exhaustive]`, as mixing these two features is not allowed under this RFC. Situations where this can be important include those where `Foo` is some token for some resource and where fabricating a `Foo` may prove dangerous or worse unsound. This is however no different than carelessly adding @@ -1679,7 +1716,7 @@ such as an attribute or item modifier: ```rust pub mod foo { - #[allow_private_defaults] + #[allow_private_defaults(gamma)] pub struct Alpha { beta: u8 = 42, gamma: bool = true, From 1f380fcc7e50a35609a97e546ff1d3a9f1d77c8c Mon Sep 17 00:00:00 2001 From: Esteban Kuber Date: Wed, 28 Aug 2024 14:38:37 -0700 Subject: [PATCH 29/37] Fix links and typo --- text/0000-default-field-values.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/text/0000-default-field-values.md b/text/0000-default-field-values.md index 7a7a8b71680..75ad7fa77f2 100644 --- a/text/0000-default-field-values.md +++ b/text/0000-default-field-values.md @@ -259,6 +259,8 @@ linearly with the number of fields. ### Imperfect derives +[perfect derives]: https://smallcultfollowing.com/babysteps/blog/2022/04/12/implied-bounds-and-perfect-derive/ + One thing to notice, is that taking default values into consideration during the desugaring of `#[derive(Default)]` would allow to side-step the issue of our lack of [perfect derives], by making the desugaring syntactically check which @@ -639,9 +641,9 @@ it had been attached to a `struct` with the same fields and field visibility. ### Interaction with `#[default]` -[`#[default]`]: https://github.com/rust-lang/rfcs/pull/3107 +[default]: https://github.com/rust-lang/rfcs/pull/3107 -It is possible today to specify a [`#[default]` variant] in an enum so that it +It is possible today to specify a [`#[default]` variant][default] in an enum so that it can be `#[derive(Default)]`. A variant marked with `#[default]` will use defaulted fields when present. @@ -1653,7 +1655,7 @@ let _ = Foo { .. }; // Currently forbidden ## Privacy: building `struct`s with private defaulted fields -[privacy]: #future-privacy +[future-privacy]: #future-privacy [RFC-0736]: https://github.com/rust-lang/rfcs/blob/master/text/0736-privacy-respecting-fru.md From 318f5e60e983e24594ace66705531f6a7793526e Mon Sep 17 00:00:00 2001 From: Esteban Kuber Date: Wed, 28 Aug 2024 14:47:46 -0700 Subject: [PATCH 30/37] Change FRU wording and links --- text/0000-default-field-values.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/text/0000-default-field-values.md b/text/0000-default-field-values.md index 75ad7fa77f2..8dd4c80ac98 100644 --- a/text/0000-default-field-values.md +++ b/text/0000-default-field-values.md @@ -44,16 +44,16 @@ let _ = Pet::default(); ### For `struct`s -[FRU]: https://doc.rust-lang.org/1.31.0/book/ch05-01-defining-structs.html#creating-instances-from-other-instances-with-struct-update-syntax +[update-syntax]: https://doc.rust-lang.org/book/ch05-01-defining-structs.html#creating-instances-from-other-instances-with-struct-update-syntax Rust allows you to create an instance of a `struct` using the struct literal syntax `Foo { bar: expr, baz: expr }`. To do so, all fields in the `struct` must be assigned a value. This makes it inconvenient to create large `struct`s -whose fields usually receive the same values. -*[Functional record updates (FRU)][FRU]* can reduce noise when a `struct` -derives `Default`, but are also invalid when the `struct` has inaccessible -fields and do not allow the creation of an `impl` where *some* fields are -mandatory. +whose fields usually receive the same values. It also allows you construct [a +new instance of the same `struct` by consuming some (or all) of the fields of +an existing value][update-syntax], which can reduce noise when a `struct` derives `Default`, +but are also invalid when the `struct` has inaccessible fields and do not allow +the creation of an `impl` where *some* fields are mandatory. To work around these shortcomings, you can create constructor functions: @@ -1693,7 +1693,7 @@ functions in `beta` to access `field`'s value, the value `42` or any other remains at all times private to `alpha`. Therefore, privacy, and by extension soundness, is preserved. -This used to be the behavior the Functional Record Update syntax had *before* +This used to be the behavior the [Functional Record Update syntax had *before* [RFC-0736], where we previously allowed for the construction of a value with private fields with values from a base expression. From 3148a9f9a6f0121be5562a5d3fe63edadda6fb0a Mon Sep 17 00:00:00 2001 From: Esteban Kuber Date: Mon, 23 Sep 2024 17:41:41 -0700 Subject: [PATCH 31/37] Update outdated `const` context message --- text/0000-default-field-values.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/text/0000-default-field-values.md b/text/0000-default-field-values.md index 8dd4c80ac98..beb5418fb5f 100644 --- a/text/0000-default-field-values.md +++ b/text/0000-default-field-values.md @@ -1036,9 +1036,8 @@ There are many reasons for this restriction: is total than it is to ensure that it is deterministic; thus, while it is regrettable, this is an acceptable trade-off. -+ Finally, note that `const fn`s, when coupled with planned future extensions, - can become quite expressive. For example, it is already planned that `loop`s, - `match`es, `let` statements, and `panic!(..)`s will be allowed. ++ Finally, note that `const fn`s, can become quite expressive. For example, + it is possible to use `loop`s, `match`es, `let` statements, and `panic!(..)`s. Another feasible extension in the future is allocation. Therefore, constant expressions should be enough to satisfy most expressive From 79e9f1b115b8e2794605e981a9b23a3f7e27eb27 Mon Sep 17 00:00:00 2001 From: Esteban Kuber Date: Mon, 23 Sep 2024 17:49:37 -0700 Subject: [PATCH 32/37] Tweak wording Co-authored-by: Ralf Jung --- text/0000-default-field-values.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-default-field-values.md b/text/0000-default-field-values.md index beb5418fb5f..bd6b201c043 100644 --- a/text/0000-default-field-values.md +++ b/text/0000-default-field-values.md @@ -1096,7 +1096,7 @@ Among the drawbacks are: introducing `Foo { .. }` is essentially cost-free in terms of the complexity budget. It is arguably even cost-negative. -+ By writing `Foo { .. }`, you have there is explicit indication that default ++ By writing `Foo { .. }`, there is explicit indication that default values are being used; this enhances local reasoning further. This RFC requires the `..` to get defaulted fields because it wants to continue From b6a05d0c5ac41420b706b90967309a8b5c2f4046 Mon Sep 17 00:00:00 2001 From: Esteban Kuber Date: Mon, 23 Sep 2024 17:53:07 -0700 Subject: [PATCH 33/37] Remove caveat that is no longer true --- text/0000-default-field-values.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/text/0000-default-field-values.md b/text/0000-default-field-values.md index bd6b201c043..7e11b76fe9c 100644 --- a/text/0000-default-field-values.md +++ b/text/0000-default-field-values.md @@ -1943,8 +1943,7 @@ these values can be accessed by instantiating `Config { .. }.height`, as long as `height` is visible in the current scope. Note that the *opposite* is supported, writing that code will compile, so any -API author that wants to make these `const` values on the type can (only -restriction is that `Self` isn't accepted in current `const` contexts): +API author that wants to make these `const` values on the type can: ```rust struct Config { From e4101ccde9aa7612f0e75e84df2fb077638bf049 Mon Sep 17 00:00:00 2001 From: Esteban Kuber Date: Mon, 23 Sep 2024 18:02:22 -0700 Subject: [PATCH 34/37] expand iif --- text/0000-default-field-values.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-default-field-values.md b/text/0000-default-field-values.md index 7e11b76fe9c..af8de0aea62 100644 --- a/text/0000-default-field-values.md +++ b/text/0000-default-field-values.md @@ -1579,7 +1579,7 @@ this RFC so that constructor functions are regained if so desired. let config = Config { width: 640, height: 480, .. }; ``` - then adding a field to `Config` can only happen iff that field + then adding a field to `Config` can only happen if and only if that field is provided a default value. This arrangement, while diminishing the usefulness of `#[non_exhaustive]`, From 3ed9f27f2e1597982af7e4107aeacdb4adc9451d Mon Sep 17 00:00:00 2001 From: Esteban Kuber Date: Mon, 23 Sep 2024 18:03:15 -0700 Subject: [PATCH 35/37] fix typo --- text/0000-default-field-values.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-default-field-values.md b/text/0000-default-field-values.md index af8de0aea62..b99b693705d 100644 --- a/text/0000-default-field-values.md +++ b/text/0000-default-field-values.md @@ -1122,7 +1122,7 @@ design around the lack of these in the language are: A prior version of this RFC, from which part of the contents in this version were sourced, exists at https://github.com/Centril/rfcs/pull/19. -This RFC was informed by a [lenghty discussion in internals.rust-lang.org](https://internals.rust-lang.org/t/pre-pre-rfc-syntactic-sugar-for-default-default/13234/75) +This RFC was informed by a [lengthy discussion in internals.rust-lang.org](https://internals.rust-lang.org/t/pre-pre-rfc-syntactic-sugar-for-default-default/13234/75) from a few years prior. Another prior RFC for the same feature is at https://github.com/rust-lang/rfcs/pull/1806. From 096fa1c0c0a466afd61e3d603ab03b5a41322d07 Mon Sep 17 00:00:00 2001 From: Esteban Kuber Date: Tue, 29 Oct 2024 10:18:16 -0700 Subject: [PATCH 36/37] Update text/0000-default-field-values.md Co-authored-by: Felix S Klock II --- text/0000-default-field-values.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/0000-default-field-values.md b/text/0000-default-field-values.md index b99b693705d..5203cfd205b 100644 --- a/text/0000-default-field-values.md +++ b/text/0000-default-field-values.md @@ -1047,8 +1047,8 @@ There are many reasons for this restriction: As an alternative to the proposed design is either explicitly writing out `..Default::default()` or extending the language such that `Foo { .. }` becomes -sugar for `Foo { ..Default::default() }`. While the latter idea does not satisfy -any of the [motivation] set out, the former does to a small extent. +sugar for `Foo { ..Default::default() }`. While the former idea does not satisfy +any of the [motivation] set out, the latter does to a small extent. In particular, `Foo { .. }` as sugar slightly improves ergonomics. However, it has some notable problems: From 80e8d0b6ec6225acf81ebcf9f9cb2423e0c184c6 Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Sun, 3 Nov 2024 20:47:20 +0000 Subject: [PATCH 37/37] Prepare RFC 3681 to be merged --- ...0-default-field-values.md => 3681-default-field-values.md} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename text/{0000-default-field-values.md => 3681-default-field-values.md} (99%) diff --git a/text/0000-default-field-values.md b/text/3681-default-field-values.md similarity index 99% rename from text/0000-default-field-values.md rename to text/3681-default-field-values.md index 5203cfd205b..1b7e998263d 100644 --- a/text/0000-default-field-values.md +++ b/text/3681-default-field-values.md @@ -1,7 +1,7 @@ - Feature Name: `default_field_values` - Start Date: 2024-08-22 -- RFC PR: https://github.com/rust-lang/rfcs/pull/3681 -- Rust Issue: +- RFC PR: [rust-lang/rfcs#3681](https://github.com/rust-lang/rfcs/pull/3681) +- Tracking Issue: [rust-lang/rust#132162](https://github.com/rust-lang/rust/issues/132162) # Summary [summary]: #summary