From 0a86f65a4f016d9ccb5eacfc776310034bd01a2d Mon Sep 17 00:00:00 2001 From: Ashley Mannix Date: Sat, 3 Dec 2016 09:00:41 +1000 Subject: [PATCH 1/7] default fields rfc --- text/0000-default-fields.md | 305 ++++++++++++++++++++++++++++++++++++ 1 file changed, 305 insertions(+) create mode 100644 text/0000-default-fields.md diff --git a/text/0000-default-fields.md b/text/0000-default-fields.md new file mode 100644 index 00000000000..7611d5636cd --- /dev/null +++ b/text/0000-default-fields.md @@ -0,0 +1,305 @@ +- Feature Name: Default Fields +- Start Date: 2016-12-03 +- RFC PR: +- Rust Issue: + +# Summary +[summary]: #summary + +Allow `struct` definitions to supply default values for individual fields, and then allow those fields to be omitted from `struct` initialisation: + +```rust +struct Foo { + a: &'static str, + b: bool = true, + c: i32, +} + +let foo = Foo { + a: "Hello", + c: 42, +}; +``` + +# Motivation +[motivation]: #motivation + +Rust allows you to create an instance of a `struct` using a literal syntax. This requires that all fields in the `struct` are assigned a value, so it can be inconvenient for large `struct`s whose fields usually receive the same values. Literals also can't be used to initialise `struct`s with innaccessible private fields. Functional record updates can reduce noise when a `struct` derives `Default`, but are also invalid when the `struct` has private fields. + +To work around these shortcomings, you can create constructor functions: + +```rust +struct Foo { + a: &'static str, + b: bool, + c: i32, +} + +impl Foo { + // Constructor function. + fn new(a: &'static str, c: i32) -> Self { + Foo { + a: a, + b: true, + c: c + } + } +} + +let foo = Foo::new("Hello", 42); +``` + +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, like [`process::Command`](https://doc.rust-lang.org/stable/std/process/struct.Command.html) in the standard library. Builders enable more advanced initialisation, but need additional boilerplate. + +This RFC proposes a solution to improve `struct` literal ergonomics, so they can be used for `struct`s with private fields, and to reduce initialisation boilerplate for simple scenarios. This is achieved by letting callers omit fields from initialisation when a default is specified for that field. This syntax also allows allows fields to be added to `struct`s in a backwards compatible way, by providing defaults for new fields. + +Field defaults allow a caller to initialise a `struct` with default values without needing builders or a constructor function: + +```rust +struct Foo { + a: &'static str = "Hello", + b: bool = true, + c: i32 = 42, +} + +// Overriding a single field default +let foo = Foo { + b: false +}; + +// Override multiple field defaults +let foo = Foo { + a: "Overriden", + c: 1, +}; + +// Override no field defaults +let foo = Foo {}; +``` + +# Detailed design +[design]: #detailed-design + +## Grammar + +In the definition of a `struct`, a default value expression can be optionally supplied for a field: + +``` +struct_field ::= vis? ident ':' type_path | + vis? ident ':' type_path '=' expr +``` + +The syntax is modeled after constant expressions. Field defaults for tuple structs are not supported. + +## Interpretation + +The value of a field default must be a compile-time expression. So any expression that's valid as a `const` can be used as a field default. This ensures values that aren't specified by the caller are deterministic and cheap to produce. A type doesn't need to derive `Default` to be valid as a field default. + +Valid: + +```rust +struct Foo { + a: &'static str, + b: bool = true, + c: i32, +} +``` + +Invalid: + +```rust +struct Foo { + a: &'static str, + b: Vec = Vec::new(), + ^^^^^^^^^^ + // error: calls in field defaults are limited to struct and enum constructors + c: i32, +} +``` + +The above error is based on `E0015` for trying to initialise a constant with a non-constant expression. As the scope of constant expressions changes this message will change too. + +Field defaults are like a shorthand for the 'real' initialiser, where values for missing fields are added with the supplied default expression: + +```rust +let foo = Foo { + a: "Hello", + c: 42, +}; +``` + +is equivalent to: + +```rust +let foo = Foo { + a: "Hello", + b: true, + c: 42, +}; +``` + +The mechanism isn't exactly a shorthand because the structure can still be initialised using field defaults even if `b` is private. The caller still can't interact with private fields directly so privacy isn't violated. + +When a caller doesn't supply a field value during initialisation and there is no default available then the `E0063` missing field error applies. + +## Order of precedence + +Supplied field values take precedence over field defaults: + +```rust +// `b` is `false`, even though the field default is `true` +let foo = Foo { + a: "Hello", + b: false, + c: 42, +}; +``` + +Supplied field values in functional updates take precedence over field defaults: + +```rust +// `b` is `false`, even though the field default is `true` +let foo = Foo { + a: "Hello", + c: 0, + ..Foo { + a: "Hello", + b: false, + c: 0 + } +}; +``` + +## Deriving `Default` + +When deriving `Default`, supplied field defaults are used instead of the type default. This is a feature of `#[derive(Default)]`. + +```rust +#[derive(Default)] +struct Foo { + a: &'static str, + b: bool = true, + c: i32, +} + +// `b` is `true`, even though `bool::default()` is `false` +let foo = Foo::default(); +``` + +Field defaults allow `#[derive(Default)]` to be used more widely because the types of fields with default values don't need to implement `Default`. + +## Enabling backwards compatibility + +With no special syntax, additional fields can be added to a struct in a non-breaking fashion. Say we have the following API and consumer: + +```rust +mod data { + pub struct Foo { + pub a: &'static str, + pub c: i32, + } +} + +let foo = data::Foo { + a: "Hello", + c: 42, +} +``` + +We can add a new field `b` to this `struct` with a default value, and the calling code doesn't change: + +```rust +mod data { + pub struct Foo { + pub a: &'static str, + pub b: bool = true, + pub c: i32, + } +} + +let foo = data::Foo { + a: "Hello", + c: 42, +} +``` + +By using field defaults, callers can use `struct` literals without having to know about any private fields: + +```rust +mod data { + pub struct Foo { + pub a: &'static str, + pub c: i32, + private_field: bool = true + } +} + +let foo = data::Foo { + a: "Hello", + c: 42, +} +``` + +## Field Privacy + +Default values for fields are opted into by the `struct` definition, rather than the caller initialising the structure. Field privacy doesn't need to be violated to initialise a structure. + +# Drawbacks +[drawbacks]: #drawbacks + +Field defaults are limited to constant expressions. This means there are values that can't be used as defaults, so any value that requires allocation, including common collections like `Vec::new()`. It's expected that users will use a constructor function or builder for initialisers that require allocations. + +# Alternatives +[alternatives]: #alternatives + +## Allow arbitrary expressions instead of just constant expressions + +Allowing arbitrary expressions as field defaults would make this feature more powerful. However, limiting field defaults to constant expressions maintains the expectation that struct literals are cheap and deterministic. The same isn't true when arbitrary expressions that could reasonably panic or block on io are allowed. + +For complex initialisation logic, builders are the preferred option because they don't carry this same expectation. + +## Allow `Default::default()` instead of just constant expressions + +An alternative to allowing any expression as a default value is allowing `Default::default()`, which is expected to be cheap and deterministic: + +```rust +struct Foo { + a: &'static str, + b: Vec = Vec::default(), + c: i32, +} + +let foo = Foo { + a: "Hello", + c: 42, +} +``` + +It could be argued that supporting `default()` is an artificial constraint that doesn't prevent arbitrary expressions. The difference is that `Default` has an expectation of being cheap, so using it to inject logic into field initialisation is an obvious code smell. + +Allowing functionality to be injected into data initialisation through `default()` means struct literals may have runtime costs that aren't ever surfaced to the caller. This goes against the expectation that literal expressions have small, predictable runtime cost and are totally deterministic (and trivially predictable). + +## Type inference for fields with defaults + +Reduce the amount of code needed to define a `struct` by allowing type inference for field defaults: + +```rust +struct Foo { + a: &'static str, + b = Bar, + c: i32, +} +``` + +This has the effect of simplifying the definition, but also requiring readers to manually work out what the type of the right-hand-side is. This could be less of an issue with IDE support. + +## Explicit syntax for opting into field defaults + +Field defaults could require callers to use an opt-in syntax like `..`. This would make it clearer to callers that additional code could be run on struct initialisation, weakening arguments against more powerful default expressions. However it would prevent field default from being used to maintain backwards compatibility, and reduce overall ergonomics. If it's unclear whether or not a particular caller will use a default field value then its addition can't be treated as a non-breaking change. + +This could be worked around by adding a private field to a `struct` that does nothing, forcing a caller to opt-in to potential field defaults. + +The goal of the design proposed in this RFC is to let users build a struct as if its default fields weren't there. + +# Unresolved questions +[unresolved]: #unresolved-questions From dfe10f71c0cf6a6c7277130db0cf4111ebf8cf07 Mon Sep 17 00:00:00 2001 From: Ashley Mannix Date: Sun, 18 Dec 2016 09:36:47 +1000 Subject: [PATCH 2/7] add explicit syntax --- text/0000-default-fields.md | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/text/0000-default-fields.md b/text/0000-default-fields.md index 7611d5636cd..08684f9df25 100644 --- a/text/0000-default-fields.md +++ b/text/0000-default-fields.md @@ -18,6 +18,7 @@ struct Foo { let foo = Foo { a: "Hello", c: 42, + .. }; ``` @@ -64,17 +65,19 @@ struct Foo { // Overriding a single field default let foo = Foo { - b: false + b: false, + .. }; // Override multiple field defaults let foo = Foo { a: "Overriden", c: 1, + .. }; // Override no field defaults -let foo = Foo {}; +let foo = Foo { .. }; ``` # Detailed design @@ -125,6 +128,7 @@ Field defaults are like a shorthand for the 'real' initialiser, where values for let foo = Foo { a: "Hello", c: 42, + .. }; ``` @@ -142,6 +146,19 @@ The mechanism isn't exactly a shorthand because the structure can still be initi When a caller doesn't supply a field value during initialisation and there is no default available then the `E0063` missing field error applies. +Field defaults are only considered for missing fields when the caller supplies a `..` at the end of the initialiser. Otherwise the standard `E0063` error applies with additional help when the field has a default value available: + +```rust +let foo = Foo { + a: "Hello", + c: 42, +}; + +// error: missing field `b` in initializer of `Foo`. +// help: `b` has a default value. Try adding `..` so its default value will be used: +// `let foo = Foo { a: "Hello", c: 42, .. }` +``` + ## Order of precedence Supplied field values take precedence over field defaults: @@ -197,16 +214,18 @@ mod data { pub struct Foo { pub a: &'static str, pub c: i32, + _marker: () = () } } let foo = data::Foo { a: "Hello", c: 42, + .. } ``` -We can add a new field `b` to this `struct` with a default value, and the calling code doesn't change: +Using a private marker field with a default value forces callers to opt-in to field defaults. We can now add a new field `b` to this `struct` with a default value, and the calling code doesn't change: ```rust mod data { @@ -214,12 +233,14 @@ mod data { pub a: &'static str, pub b: bool = true, pub c: i32, + _marker: () = () } } let foo = data::Foo { a: "Hello", c: 42, + .. } ``` @@ -237,6 +258,7 @@ mod data { let foo = data::Foo { a: "Hello", c: 42, + .. } ``` @@ -293,13 +315,11 @@ struct Foo { This has the effect of simplifying the definition, but also requiring readers to manually work out what the type of the right-hand-side is. This could be less of an issue with IDE support. -## Explicit syntax for opting into field defaults - -Field defaults could require callers to use an opt-in syntax like `..`. This would make it clearer to callers that additional code could be run on struct initialisation, weakening arguments against more powerful default expressions. However it would prevent field default from being used to maintain backwards compatibility, and reduce overall ergonomics. If it's unclear whether or not a particular caller will use a default field value then its addition can't be treated as a non-breaking change. +## Implicit syntax for opting into field defaults -This could be worked around by adding a private field to a `struct` that does nothing, forcing a caller to opt-in to potential field defaults. +Invoke field defaults implicitely instead of requiring a `..` in the struct initialiser. This would be more in line with how other languages handle default values, but is less explicit. It would also be different from pattern matching for structures, that require a `..` to ignore unnamed fields. -The goal of the design proposed in this RFC is to let users build a struct as if its default fields weren't there. +A future RFC could propose making the `..` optional for all places it's required when dealing with structures. # Unresolved questions [unresolved]: #unresolved-questions From 31923eb1fb82767c3a6a3e2145038d8f0d77b601 Mon Sep 17 00:00:00 2001 From: Ashley Mannix Date: Tue, 20 Dec 2016 12:17:37 +1000 Subject: [PATCH 3/7] naive how we teach this section --- text/0000-default-fields.md | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/text/0000-default-fields.md b/text/0000-default-fields.md index 08684f9df25..e926ce45a67 100644 --- a/text/0000-default-fields.md +++ b/text/0000-default-fields.md @@ -88,8 +88,18 @@ let foo = Foo { .. }; In the definition of a `struct`, a default value expression can be optionally supplied for a field: ``` -struct_field ::= vis? ident ':' type_path | - vis? ident ':' type_path '=' expr +struct_field : vis? ident ':' type_path | + vis? ident ':' type_path '=' expr +``` + +Initialisers can then opt-in to use field defaults for missing fields by adding `..` to the end of the initialiser: + +``` +struct_init_fields : struct_field_init ? [ ',' struct_field_init ] * + +struct_init : '{' + struct_init_fields [ ".." | ".." expr ] ? +'}' ``` The syntax is modeled after constant expressions. Field defaults for tuple structs are not supported. @@ -266,6 +276,19 @@ let foo = data::Foo { Default values for fields are opted into by the `struct` definition, rather than the caller initialising the structure. Field privacy doesn't need to be violated to initialise a structure. +# How We Teach This +[how-we-teach-this]: #how-we-teach-this + +Field defaults look similar to functional record updates, but solve different problems. New users could be confused by the similarity and be unsure when to use either feature. We can easily distinguish the two for new users: + +- If you're in control of the structure definition, you should opt for field defaults. +- If you're only initialising structures, you should opt for functional record updates. + +As for constructors, structure literals and builders, we can recommend the following rule of thumb: + +- If your initialiser needs non-constant expressions or various default values can be logically grouped, you should opt for constructor functions. +- If your initialiser needs non-constant expressions, has many common permutations or is otherwise too complex for structure literals or constructors, then you should opt for builders. + # Drawbacks [drawbacks]: #drawbacks From c1dde0d6a8bd2f612b0ad2cbd19bcc8625acc81d Mon Sep 17 00:00:00 2001 From: Ashley Mannix Date: Tue, 20 Dec 2016 17:31:45 +1000 Subject: [PATCH 4/7] tidy up how we teach this --- text/0000-default-fields.md | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/text/0000-default-fields.md b/text/0000-default-fields.md index e926ce45a67..51f34696ef3 100644 --- a/text/0000-default-fields.md +++ b/text/0000-default-fields.md @@ -152,7 +152,7 @@ let foo = Foo { }; ``` -The mechanism isn't exactly a shorthand because the structure can still be initialised using field defaults even if `b` is private. The caller still can't interact with private fields directly so privacy isn't violated. +The mechanism isn't exactly a shorthand because the `struct` can still be initialised using field defaults even if `b` is private. The caller still can't interact with private fields directly so privacy isn't violated. When a caller doesn't supply a field value during initialisation and there is no default available then the `E0063` missing field error applies. @@ -274,20 +274,17 @@ let foo = data::Foo { ## Field Privacy -Default values for fields are opted into by the `struct` definition, rather than the caller initialising the structure. Field privacy doesn't need to be violated to initialise a structure. +Default values for fields are opted into by the `struct` definition, rather than the caller initialising the `struct`. Field privacy doesn't need to be violated to initialise a `struct`. # How We Teach This [how-we-teach-this]: #how-we-teach-this Field defaults look similar to functional record updates, but solve different problems. New users could be confused by the similarity and be unsure when to use either feature. We can easily distinguish the two for new users: -- If you're in control of the structure definition, you should opt for field defaults. -- If you're only initialising structures, you should opt for functional record updates. +- If you're in control of the `struct` definition, you can use field defaults. +- If you're only initialising `struct`s that don't have defaults or private fields, you can use functional record updates. -As for constructors, structure literals and builders, we can recommend the following rule of thumb: - -- If your initialiser needs non-constant expressions or various default values can be logically grouped, you should opt for constructor functions. -- If your initialiser needs non-constant expressions, has many common permutations or is otherwise too complex for structure literals or constructors, then you should opt for builders. +Field defaults are a tool for producers and functional record updates are a tool for consumers. # Drawbacks [drawbacks]: #drawbacks @@ -340,9 +337,9 @@ This has the effect of simplifying the definition, but also requiring readers to ## Implicit syntax for opting into field defaults -Invoke field defaults implicitely instead of requiring a `..` in the struct initialiser. This would be more in line with how other languages handle default values, but is less explicit. It would also be different from pattern matching for structures, that require a `..` to ignore unnamed fields. +Invoke field defaults implicitely instead of requiring a `..` in the initialiser. This would be more in line with how other languages handle default values, but is less explicit. It would also be different from pattern matching for `struct`s, that require a `..` to ignore unnamed fields. -A future RFC could propose making the `..` optional for all places it's required when dealing with structures. +A future RFC could propose making the `..` optional for all places it's required when dealing with `struct`s. # Unresolved questions [unresolved]: #unresolved-questions From 700571b61bb51f335da0f5b3d15d5393c6f0247a Mon Sep 17 00:00:00 2001 From: Ashley Mannix Date: Sun, 8 Jan 2017 01:53:27 +1000 Subject: [PATCH 5/7] expand how we teach this --- text/0000-default-fields.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/text/0000-default-fields.md b/text/0000-default-fields.md index 51f34696ef3..f7adfb28fa6 100644 --- a/text/0000-default-fields.md +++ b/text/0000-default-fields.md @@ -286,6 +286,8 @@ Field defaults look similar to functional record updates, but solve different pr Field defaults are a tool for producers and functional record updates are a tool for consumers. +We should document field defaults in the Rust Reference sections 6.1.5 "Structs" and 7.2.4 "Struct expressions", with a one-sentence mention in 6.1.6 "Enumerations" that struct-like enum variants can have default fields just like structs. + # Drawbacks [drawbacks]: #drawbacks From 1a79390c50564664f15e566135508a53345b6ade Mon Sep 17 00:00:00 2001 From: Ashley Mannix Date: Tue, 13 Jun 2017 10:55:07 +1000 Subject: [PATCH 6/7] add symmetry with pattern destructuring --- text/0000-default-fields.md | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/text/0000-default-fields.md b/text/0000-default-fields.md index f7adfb28fa6..54135d3cf02 100644 --- a/text/0000-default-fields.md +++ b/text/0000-default-fields.md @@ -272,9 +272,25 @@ let foo = data::Foo { } ``` +## Symmetry with Destructuring + +Field defaults allow `struct`s to be non-exhaustively constructed and deconstructed using the same syntax: + +```rust +// Construct +let foo = Foo { + a: "Hello", + c: 42, + .. +} + +// Deconstruct +let Foo { a, c, .. } = foo; +``` + ## Field Privacy -Default values for fields are opted into by the `struct` definition, rather than the caller initialising the `struct`. Field privacy doesn't need to be violated to initialise a `struct`. +Default values for fields are opted into by the `struct` definition, rather than the caller initialising the `struct`. Field privacy doesn't need to be violated to initialise a `struct` with private default values. # How We Teach This [how-we-teach-this]: #how-we-teach-this From 7c55f73adb1c3ffdf3ae9decbe8af8d8bbe06d6f Mon Sep 17 00:00:00 2001 From: Ashley Mannix Date: Wed, 6 Sep 2017 14:18:09 +1000 Subject: [PATCH 7/7] remove back compat and add fru to alternatives --- text/0000-default-fields.md | 61 +++---------------------------------- 1 file changed, 4 insertions(+), 57 deletions(-) diff --git a/text/0000-default-fields.md b/text/0000-default-fields.md index 54135d3cf02..7ab2b5c23ba 100644 --- a/text/0000-default-fields.md +++ b/text/0000-default-fields.md @@ -215,63 +215,6 @@ let foo = Foo::default(); Field defaults allow `#[derive(Default)]` to be used more widely because the types of fields with default values don't need to implement `Default`. -## Enabling backwards compatibility - -With no special syntax, additional fields can be added to a struct in a non-breaking fashion. Say we have the following API and consumer: - -```rust -mod data { - pub struct Foo { - pub a: &'static str, - pub c: i32, - _marker: () = () - } -} - -let foo = data::Foo { - a: "Hello", - c: 42, - .. -} -``` - -Using a private marker field with a default value forces callers to opt-in to field defaults. We can now add a new field `b` to this `struct` with a default value, and the calling code doesn't change: - -```rust -mod data { - pub struct Foo { - pub a: &'static str, - pub b: bool = true, - pub c: i32, - _marker: () = () - } -} - -let foo = data::Foo { - a: "Hello", - c: 42, - .. -} -``` - -By using field defaults, callers can use `struct` literals without having to know about any private fields: - -```rust -mod data { - pub struct Foo { - pub a: &'static str, - pub c: i32, - private_field: bool = true - } -} - -let foo = data::Foo { - a: "Hello", - c: 42, - .. -} -``` - ## Symmetry with Destructuring Field defaults allow `struct`s to be non-exhaustively constructed and deconstructed using the same syntax: @@ -359,5 +302,9 @@ Invoke field defaults implicitely instead of requiring a `..` in the initialiser A future RFC could propose making the `..` optional for all places it's required when dealing with `struct`s. +## Enhancements to functional record updates + +Instead of adding a new language feature, provide some alternative desugaring for functional record updates that avoids the problems outlined in [736](https://github.com/rust-lang/rfcs/blob/master/text/0736-privacy-respecting-fru.md) and still works with private fields. + # Unresolved questions [unresolved]: #unresolved-questions