From d3b1a8f436f764e2c71c418953c8e9d7f499207f Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Thu, 11 Oct 2018 06:26:14 +0200 Subject: [PATCH 01/12] rfc, gradual-struct-init: init template. --- text/0000-gradual-struct-init.md | 49 ++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 text/0000-gradual-struct-init.md diff --git a/text/0000-gradual-struct-init.md b/text/0000-gradual-struct-init.md new file mode 100644 index 00000000000..ea4000784da --- /dev/null +++ b/text/0000-gradual-struct-init.md @@ -0,0 +1,49 @@ +- Feature Name: `gradual_struct_init` +- Start Date: 2018-10-11 +- RFC PR: _ +- Rust Issue: _ + +# Summary +[summary]: #summary + +TODO + +# Motivation +[motivation]: #motivation + +TODO + +# Guide-level explanation +[guide-level-explanation]: #guide-level-explanation + +TODO + +# Reference-level explanation +[reference-level-explanation]: #reference-level-explanation + +TODO + +# Drawbacks +[drawbacks]: #drawbacks + +TODO + +# Rationale and alternatives +[rationale-and-alternatives]: #rationale-and-alternatives + +TODO + +# Prior art +[prior-art]: #prior-art + +TODO + +# Unresolved questions +[unresolved-questions]: #unresolved-questions + +TODO + +# Future work +[future-work]: #future-work + +TODO From ffac5a15356818ace1ff47caea2d848b55ec502b Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Mon, 15 Oct 2018 02:40:54 +0200 Subject: [PATCH 02/12] rfc, gradual-struct-init: dump work. --- text/0000-gradual-struct-init.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/text/0000-gradual-struct-init.md b/text/0000-gradual-struct-init.md index ea4000784da..2b8f0e1a296 100644 --- a/text/0000-gradual-struct-init.md +++ b/text/0000-gradual-struct-init.md @@ -6,7 +6,17 @@ # Summary [summary]: #summary -TODO +Permit the gradual initialization of structs like so: + +```rust +struct Point { x: T, y: T } + +let pt: Point<_>; +pt.x = 42; +pt.y = 24; + +drop(pt); +``` # Motivation [motivation]: #motivation From 45d43766b137af6010823195aeb4bc095d18cdf6 Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Mon, 22 Oct 2018 15:47:05 +0200 Subject: [PATCH 03/12] rfc, gradual-struct-init: more work. --- text/0000-gradual-struct-init.md | 359 +++++++++++++++++++++++++++++++ 1 file changed, 359 insertions(+) diff --git a/text/0000-gradual-struct-init.md b/text/0000-gradual-struct-init.md index 2b8f0e1a296..fc69641b11d 100644 --- a/text/0000-gradual-struct-init.md +++ b/text/0000-gradual-struct-init.md @@ -21,11 +21,370 @@ drop(pt); # Motivation [motivation]: #motivation +The main motivation of this RFC is + TODO # Guide-level explanation [guide-level-explanation]: #guide-level-explanation +[structurally typed]: https://en.wikipedia.org/wiki/Structural_type_system +[nominally typed]: https://en.wikipedia.org/wiki/Nominal_type_system +[product type]: https://en.wikipedia.org/wiki/Product_type +[tuple type]: https://doc.rust-lang.org/nightly/reference/types.html#tuple-types +[struct type]: https://doc.rust-lang.org/nightly/reference/types.html#struct-types +[visibility]: https://doc.rust-lang.org/nightly/reference/visibility-and-privacy.html +[place]: https://doc.rust-lang.org/nightly/reference/expressions.html#place-expressions-and-value-expressions +[variable]: https://doc.rust-lang.org/nightly/reference/variables.html + +## Definitions, concepts, and vocabulary + +Let's first get some definitions out of the way; if you are familiar with the +Rust language, you can skip this section. + +- A *local binding* or *[variable]* is a name for a component on the stack + which holds a value. An example (1): + + ```rust + let foo = 1; + ``` + + A binding may be *immutable*, which `foo` is, or *mutable*, which `bar` is (2): + + ```rust + let mut foo = 1; + ``` + +- An *uninhabited type* is a type which cannot be constructed since it has + no values that inhabit the type. An example of such a type is: + + ```rust + enum Void {} + ``` + +- *Initialization* is the process of assigning a value to a binding or more + generally *[place]* for the first time. Examples are in (1) and (2). + +- *Reinitialization* is the process of assigning a value to a + place that has already been initialized but then moved out of. + Immutable bindings may not be reinitialized. + +- A *[tuple type]* is denoted as `(T0, ..., Tn)`, where `Ti` are types. + Tuples are *[structurally typed]* homogeneous *[product type]s*. + +- A *[struct type]*, declared as `struct Name { field: T0, ... }` is a + *[nominally typed]* homogeneous product type. Structs consist of a + set of *fields* with types `Ti`. Given a place `x` of type `Name` + it is possible to project out the value of a field with `x.field`. + + Each field of a struct type may optionally be assigned a *[visibility]*. + If no visibility is specified, it is assumed to be private by default. + Visibility is context dependent; a field may be visible in some context + but not in some other. + + A struct type can also be of the tuple variety and is then + declared with the syntax `struct Name(T0, ..., Tn);`. + In that case, the field names are positional indices (`i ∈ 0 ... n`). + + A struct type may also be of the unit variety, e.g. `struct Name;` in which + case it has no fields. + +## Proposal + +### The basics + +Suppose that you have a `struct` (1): + +```rust +struct Foo { + bar: usize, + baz: T, +} +``` + +Suppose also that you have a function that consumes a `Foo` (2): + +```rust +fn consume(_foo: Foo) {} +``` + +Currently, if you want to make a `Foo` then you have to write (3): + +```rust +let foo = Foo { + bar: 42usize, + baz: 24u8, +}; +``` + +**_We propose_** that you should be able to also write (4): + +```rust +let foo; +foo.bar = 42usize; +foo.baz = 24u8; + +consume(foo); // OK! +``` + +### Immutable and mutable bindings + +Note that in snippet (4), you are *not* mutating `foo`, `foo.bar`, or `foo.baz`. +These fields are only being initialized. If you however assign to `foo.bar` +twice as in (5): + +```rust +let foo: Foo; + +foo.bar = 42usize; +foo.baz = 24u8; + +// Error! Second time you assign to `foo.bar` but `foo` is not a mutable binding! +foo.bar = 43; + +drop(foo); +``` + +then the compiler will reject the program as ill-formed because +`foo` was not declared as a *mutable* binding with `mut foo`. +To remedy this, **_you may write_** (6): + +```rust +let mut foo: Foo; + +foo.bar = 42usize; +foo.baz = 24u8; + +foo.bar = 43; // OK! + +drop(foo); +``` + +### Definitive initialization and usage + +Note that in both (4) and (6), you can only `drop(foo)`, `consume(foo)`, +or take references to `foo` because you have fully initialized `foo`. +Suppose instead that you wrote (7): + +```rust +let mut foo; + +foo.baz = 42; + +consume(foo); // Error! `foo.bar` is not initialized. +``` + +The snippet in (7) would be *rejected* by the compiler because otherwise `foo.bar` +would be uninitialized and thus you would not have a valid `Foo`, thus causing +the type system to be unsound and the program to exhibit undefined behaviour. + +In our proposal, it is also not legal to take a reference to `foo`, as with +`&mut foo` or `&foo` while parts of it isn't initialized. You also cannot +copy `foo` before it is fully initialized. Neither may you take references to, +move, or copy the parts of `foo` (in this case `foo.bar`) that are not yet +initialized. This extends to conditional initialization. **_The compiler will +allow you to write (8)_**: + +```rust +let foo; + +foo.baz = 42; + +if random_bool() { + foo.bar = 1; +} else { + foo.bar = 2; +} + +consume(foo); // OK! +``` + +The snippet in (8) is allowed because when `consume(foo)` is reached, `foo.bar` +is provably initialized no matter what branch is taken in the conditional. +However, the following snippet would be rejected because `foo.bar` may be +uninitialized in the `else` branch (9): + +```rust +let foo; + +foo.baz = 42; + +if random_bool() { + foo.bar = 1; +} else { + // nothing here. +} + +consume(foo); // ERROR! `foo.bar` not initialized in `else { .. }`. +``` + +The general condition here is that all fields must be *definitely initialized*. + +### Partial initialization and referencing fields + +We previously noted that you may not move or reference parts of `foo` that are +not yet initialized. The converse also applies. **_You may reference, move, or +copy parts the parts that are initialized while the whole type isn't._** (10): + +```rust +let mut foo: Foo; + +foo.bar = 1; + +{ + let my_bar: &usize = &foo.bar; // OK! +} + + +{ + let my_mut_bar: &mut usize = &mut foo.bar; // OK! +} + +drop(foo.bar); // OK! +``` + +### Immovable "self-referential" types + +Because you are now able to construct `Foo` piecemeal, this enables you to +**_reference parts of `Foo` that have already been initialized in other parts +of `Foo`_**. For example, you may write (11): + +```rust +let foo: Foo<&usize>; + +foo.bar = 42; // <-- + // | +foo.baz = &foo.bar; // --| The compiler will ensure that this is dropped first + // so that there are no dangling references. + +// We can take a reference to `&foo`, that doesn't move `foo` anywhere: +drop(&foo); +``` + +So far so good. However, if you add a line at the end and try to move `foo` +itself, you will run into trouble (12): + +```rust +// Error! We can't move `foo` because `foo.baz` borrows `foo.bar`. +consume(foo); +``` + +With the addition of (12), an error arises because if it were otherwise, +`foo.baz` would point to the old address of `foo.bar` which would now be +invalid. This would be unsound due to the dangling reference. + +### Reinitialization + +Suppose that you have `consume`d `foo` as in (4). +In such a case, assuming `Foo` is not `Copy`, +the binding `foo` becomes uninitialized because we have moved out of `foo`. +Before this RFC, you had to reinitialize `foo` with `foo = Foo { ... }`. +**_We propose that the gradual method should work for reinitialization as well_** (13): + +```rust +let mut foo; +foo.bar = 42usize; +foo.baz = 24u8; + +consume(foo); // OK! + +foo.bar = 1; +foo.baz = 2; + +consume(foo); // OK! +``` + +### Uninhabited types + +Thus far, only `Foo` and `Foo<&usize>` have been used as the type of `foo`, +which means `T` in `Foo` has always been inhabited. +Suppose instead that you defined an uninhabited type (14): + +```rust +enum Void {} +``` + +Let's then write (16): + +```rust +let foo: Foo; + +foo.bar = 42; + +foo.baz = ???; + +consume(foo); +``` + +What do you replace `???` with? `foo.baz : Void` is an uninhabited type, +thus, there can be nothing you initialize `foo.baz` with or otherwise it +wouldn't be of an uninhabited type. Indeed, you cannot initialize `foo.baz` +and so you will never be able to form a valid value of type `Foo`. +This is to be expected, after all, you can't use the syntax +`Foo { bar: 42, baz: }` for this either. + +### Respecting privacy + +Until now, all examples have been in contexts where the fields `bar` and `baz` +have been visible. In a case where the fields are not visible, you won't be able +to construct a value of `Foo` with `Foo { bar: x, baz: y }`. The gradual +method is no different; to initialize the fields with the gradual method, +you must be able to refer to the fields. If you are not, then it follows that +you won't be able to construct a `Foo`. In other words, the following would +not be a valid Rust program (17): + +```rust +mod my_module { + pub struct Point { + x: u8, + y: u8 + } +} + +fn main() { + let pt: Point; + + pt.x = 1; // Error! pt.x is not visible in this context. + pt.y = 2; // Error! pt.y isn't visible either. + + drop(pt); +} +``` + +### No `Drop` types + +Consider a type which implements `Drop` in some way (18): + +```rust +struct SpecialDrop { + field: T, +} + +impl Drop for SpecialDrop { + fn drop(&mut self) { + + } +} +``` + +Let's then use the gradual initialization mechanism in this RFC (19): + +```rust +let sd: SpecialDrop; + +sd.field = +``` + + + +TODO + +### Tuples also work + +TODO + +### Gradual initialization in fields + TODO # Reference-level explanation From 48b7647313e5a4f283b54beb4ec2385b37f8f662 Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Tue, 23 Oct 2018 22:16:53 +0200 Subject: [PATCH 04/12] rfc, gradual-struct-init: more work. --- text/0000-gradual-struct-init.md | 80 ++++++++++++++++++-------------- 1 file changed, 45 insertions(+), 35 deletions(-) diff --git a/text/0000-gradual-struct-init.md b/text/0000-gradual-struct-init.md index fc69641b11d..31503d66e4c 100644 --- a/text/0000-gradual-struct-init.md +++ b/text/0000-gradual-struct-init.md @@ -98,7 +98,7 @@ Suppose that you have a `struct` (1): ```rust struct Foo { bar: usize, - baz: T, + qux: T, } ``` @@ -113,7 +113,7 @@ Currently, if you want to make a `Foo` then you have to write (3): ```rust let foo = Foo { bar: 42usize, - baz: 24u8, + qux: 24u8, }; ``` @@ -122,14 +122,14 @@ let foo = Foo { ```rust let foo; foo.bar = 42usize; -foo.baz = 24u8; +foo.qux = 24u8; consume(foo); // OK! ``` ### Immutable and mutable bindings -Note that in snippet (4), you are *not* mutating `foo`, `foo.bar`, or `foo.baz`. +Note that in snippet (4), you are *not* mutating `foo`, `foo.bar`, or `foo.qux`. These fields are only being initialized. If you however assign to `foo.bar` twice as in (5): @@ -137,7 +137,7 @@ twice as in (5): let foo: Foo; foo.bar = 42usize; -foo.baz = 24u8; +foo.qux = 24u8; // Error! Second time you assign to `foo.bar` but `foo` is not a mutable binding! foo.bar = 43; @@ -153,7 +153,7 @@ To remedy this, **_you may write_** (6): let mut foo: Foo; foo.bar = 42usize; -foo.baz = 24u8; +foo.qux = 24u8; foo.bar = 43; // OK! @@ -169,14 +169,15 @@ Suppose instead that you wrote (7): ```rust let mut foo; -foo.baz = 42; +foo.qux = 42; consume(foo); // Error! `foo.bar` is not initialized. ``` -The snippet in (7) would be *rejected* by the compiler because otherwise `foo.bar` -would be uninitialized and thus you would not have a valid `Foo`, thus causing -the type system to be unsound and the program to exhibit undefined behaviour. +The snippet in (7) would be *rejected* by the compiler because +otherwise `foo.bar` would be uninitialized and thus you would not +have a valid `Foo`, thus causing the type system to be unsound and +the program to exhibit undefined behaviour. In our proposal, it is also not legal to take a reference to `foo`, as with `&mut foo` or `&foo` while parts of it isn't initialized. You also cannot @@ -188,14 +189,18 @@ allow you to write (8)_**: ```rust let foo; -foo.baz = 42; +foo.bar = 42; if random_bool() { - foo.bar = 1; + foo.qux = 1; } else { - foo.bar = 2; + foo.qux = 2; } +// Nota bene: +// In this case foo.qux = if random_bool() { 1 } else { 2 }; +// is preferable as a matter of style. + consume(foo); // OK! ``` @@ -207,7 +212,7 @@ uninitialized in the `else` branch (9): ```rust let foo; -foo.baz = 42; +foo.qux = 42; if random_bool() { foo.bar = 1; @@ -223,38 +228,42 @@ The general condition here is that all fields must be *definitely initialized*. ### Partial initialization and referencing fields We previously noted that you may not move or reference parts of `foo` that are -not yet initialized. The converse also applies. **_You may reference, move, or -copy parts the parts that are initialized while the whole type isn't._** (10): +not yet initialized. The converse also applies. **_You may reference, move, +or copy parts the parts that are initialized while the whole type isn't._** (10): ```rust let mut foo: Foo; -foo.bar = 1; +foo.qux = 1; +// ^------- +// this is required because we must initialize +// all fields of `foo` at some point. + +foo.bar = 2; { let my_bar: &usize = &foo.bar; // OK! -} - +} // <- Shared borrow ends here. { let my_mut_bar: &mut usize = &mut foo.bar; // OK! -} +} // <- Mutable borrow ends here. drop(foo.bar); // OK! ``` ### Immovable "self-referential" types -Because you are now able to construct `Foo` piecemeal, this enables you to -**_reference parts of `Foo` that have already been initialized in other parts -of `Foo`_**. For example, you may write (11): +Because you are now able to construct `Foo` piecemeal, this enables you +to **_reference parts of `Foo` that have already been initialized in other +parts of `Foo`_**. For example, you may write (11): ```rust let foo: Foo<&usize>; foo.bar = 42; // <-- // | -foo.baz = &foo.bar; // --| The compiler will ensure that this is dropped first +foo.qux = &foo.bar; // --| The compiler will ensure that this is dropped first // so that there are no dangling references. // We can take a reference to `&foo`, that doesn't move `foo` anywhere: @@ -265,12 +274,12 @@ So far so good. However, if you add a line at the end and try to move `foo` itself, you will run into trouble (12): ```rust -// Error! We can't move `foo` because `foo.baz` borrows `foo.bar`. +// Error! We can't move `foo` because `foo.qux` borrows `foo.bar`. consume(foo); ``` With the addition of (12), an error arises because if it were otherwise, -`foo.baz` would point to the old address of `foo.bar` which would now be +`foo.qux` would point to the old address of `foo.bar` which would now be invalid. This would be unsound due to the dangling reference. ### Reinitialization @@ -284,12 +293,12 @@ Before this RFC, you had to reinitialize `foo` with `foo = Foo { ... }`. ```rust let mut foo; foo.bar = 42usize; -foo.baz = 24u8; +foo.qux = 24u8; consume(foo); // OK! foo.bar = 1; -foo.baz = 2; +foo.qux = 2; consume(foo); // OK! ``` @@ -311,23 +320,24 @@ let foo: Foo; foo.bar = 42; -foo.baz = ???; +foo.qux = ???; consume(foo); ``` -What do you replace `???` with? `foo.baz : Void` is an uninhabited type, -thus, there can be nothing you initialize `foo.baz` with or otherwise it -wouldn't be of an uninhabited type. Indeed, you cannot initialize `foo.baz` +What do you replace `???` with? The field `qux` is of the uninhabited type `Void`. +Thus, there can be nothing you initialize `foo.qux` with or otherwise it +wouldn't be of an uninhabited type. Indeed, you cannot initialize `foo.qux` and so you will never be able to form a valid value of type `Foo`. This is to be expected, after all, you can't use the syntax -`Foo { bar: 42, baz: }` for this either. +`Foo { bar: 42, qux: }` for this either because you cannot produce a +`` of type `Void`. ### Respecting privacy -Until now, all examples have been in contexts where the fields `bar` and `baz` +Until now, all examples have been in contexts where the fields `bar` and `qux` have been visible. In a case where the fields are not visible, you won't be able -to construct a value of `Foo` with `Foo { bar: x, baz: y }`. The gradual +to construct a value of `Foo` with `Foo { bar: x, qux: y }`. The gradual method is no different; to initialize the fields with the gradual method, you must be able to refer to the fields. If you are not, then it follows that you won't be able to construct a `Foo`. In other words, the following would From b4a47b8618fb4c1f3c422101f0bf3f82bca8002a Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Wed, 24 Oct 2018 18:15:13 +0200 Subject: [PATCH 05/12] rfc, gradual-struct-init: more work. --- text/0000-gradual-struct-init.md | 207 +++++++++++++++++++++++++++---- 1 file changed, 182 insertions(+), 25 deletions(-) diff --git a/text/0000-gradual-struct-init.md b/text/0000-gradual-struct-init.md index 31503d66e4c..ec4d13f094e 100644 --- a/text/0000-gradual-struct-init.md +++ b/text/0000-gradual-struct-init.md @@ -1,5 +1,5 @@ - Feature Name: `gradual_struct_init` -- Start Date: 2018-10-11 +- Start Date: 2018-10-24 - RFC PR: _ - Rust Issue: _ @@ -204,23 +204,23 @@ if random_bool() { consume(foo); // OK! ``` -The snippet in (8) is allowed because when `consume(foo)` is reached, `foo.bar` +The snippet in (8) is allowed because when `consume(foo)` is reached, `foo.qux` is provably initialized no matter what branch is taken in the conditional. -However, the following snippet would be rejected because `foo.bar` may be +However, the following snippet would be rejected because `foo.qux` may be uninitialized in the `else` branch (9): ```rust let foo; -foo.qux = 42; +foo.bar = 42; if random_bool() { - foo.bar = 1; + foo.qux = 1; } else { // nothing here. } -consume(foo); // ERROR! `foo.bar` not initialized in `else { .. }`. +consume(foo); // ERROR! `foo.qux` not initialized in `else { .. }`. ``` The general condition here is that all fields must be *definitely initialized*. @@ -235,9 +235,8 @@ or copy parts the parts that are initialized while the whole type isn't._** (10) let mut foo: Foo; foo.qux = 1; -// ^------- -// this is required because we must initialize -// all fields of `foo` at some point. + +drop(foo.qux); foo.bar = 2; @@ -252,6 +251,12 @@ foo.bar = 2; drop(foo.bar); // OK! ``` +Note in particular that `foo` was never fully initialized here. +As long as you don't move or borrow `foo`, this is permitted. +However, you must assign to all fields of `foo` at least once. +This restriction applies to make adding fields cause type errors +so that you can refactor more easily. + ### Immovable "self-referential" types Because you are now able to construct `Foo` piecemeal, this enables you @@ -263,7 +268,7 @@ let foo: Foo<&usize>; foo.bar = 42; // <-- // | -foo.qux = &foo.bar; // --| The compiler will ensure that this is dropped first +foo.qux = &foo.bar; // --/ The compiler will ensure that this is dropped first // so that there are no dangling references. // We can take a reference to `&foo`, that doesn't move `foo` anywhere: @@ -313,7 +318,7 @@ Suppose instead that you defined an uninhabited type (14): enum Void {} ``` -Let's then write (16): +Let's then write (15): ```rust let foo: Foo; @@ -341,7 +346,7 @@ to construct a value of `Foo` with `Foo { bar: x, qux: y }`. The gradual method is no different; to initialize the fields with the gradual method, you must be able to refer to the fields. If you are not, then it follows that you won't be able to construct a `Foo`. In other words, the following would -not be a valid Rust program (17): +not be a valid Rust program (16): ```rust mod my_module { @@ -361,46 +366,198 @@ fn main() { } ``` +[RFC 2008]: https://github.com/rust-lang/rfcs/blob/master/text/2008-non-exhaustive.md + +Per [RFC 2008], the type system will also respect `#[non_exhaustive]` +annotations with respect to gradual initialization. That is, if you mark a +`struct` with `#[non_exhaustive]` in one crate, then a downstream crate won't +be able to use the gradual syntax to make a value of the type. + ### No `Drop` types -Consider a type which implements `Drop` in some way (18): +Consider a type which implements `Drop` by printing something (17): ```rust struct SpecialDrop { - field: T, + alpha: u8, + beta: u16, + gamm: u32, } impl Drop for SpecialDrop { fn drop(&mut self) { - + println!("Dropping in a special way!"); } } ``` -Let's then use the gradual initialization mechanism in this RFC (19): +Let's then use the gradual initialization mechanism in this RFC (18): ```rust -let sd: SpecialDrop; +fn foo() { + let sd: SpecialDrop; + + sd.alpha = 1; + sd.beta = 2; -sd.field = + some_action_that_can_panic(); + + sd.gamma = 3; +} ``` +If you don't have the definition of `SpecialDrop` in your near vicinity, +it can become difficult to know whether the destructor, and thus the side effect +in `SpecialDrop::drop` will run or when it will run. In (18), to know if and +when the destructor will run, you would need to know that `SpecialDrop` has the +fields `alpha`, `beta`, and `gamma` and that `gamma` hasn't been initialized yet. +Thus, while it would be sound to allow it, if a type implements `Drop`, +the compiler will reject will not allow gradual initialization. -TODO +### Tuples and tuple structs also work -### Tuples also work +Hitherto, we have only gradually initialized `struct`s with named fields. +However, this RFC makes no distinction between those and other forms of +heterogeneous product types. In other words, **_you can also initialize +tuple structs and normal tuples with the gradualist syntax._** +That is, you can write (19): -TODO +```rust +struct Point(f32, f32); + +let pt: Point; + +pt.0 = 42.24; +pt.1 = 13.37; + +drop(pt); +``` + +as well as (20): + +```rust +fn consume_pair((x, y): (u8, u8)) { ... } + +let pt; +pt.0 = 42; +pt.1 = 24; + +consume_pair(pt); +``` ### Gradual initialization in fields -TODO +What we have said so far does not just apply to local bindings. +You can use the gradualist mechanism in fields as well. +For example, you may use the gradual notation as a sort of builder notation (21): + +```rust +struct Config { + window: WindowConfig, + runtime: RuntimeConfig, +} + +struct WindowConfig { + height: usize, + width: usize, +} + +struct RuntimeConfig { + threads: usize, + max_memory: usize, +} + +fn make_config() -> Config + let cfg; + + cfg.window.width = 1920; + cfg.window.height = 1080; + // cfg.window is definitely initialized now. + + cfg.runtime.threads = 8; + cfg.runtime.max_memory = 1024; + // cfg.runtime is definitely initialized now. + // therefore cfg is also. + + cfg +} +``` + +The usual same rules with respect to eventual definite initialization, +privacy, uninhabited types, prohibitions on `Drop` types, etc. apply +here as well. # Reference-level explanation [reference-level-explanation]: #reference-level-explanation -TODO +## Grammar + +There are no changes to the grammar. + +## Semantics + +Let: + +- `n` range over the natural numbers. + +- `Γ` be a well-formed typing environment. + +- `σ` be a well-formed type in `Γ`. + +- `typeof(e)` denote the type `σ` of an expression `e` in `Γ`. + +- `downstream(σ)` be a predicate holding iff `σ` is not defined in the current + crate. + +- `non_exhaustive(σ)` be a predicate holding iff: + - `σ` has `#[non_exhaustive]` directly applied to it, + - `downstream(σ)` holds. + +- `visible(σ, f)` be a predicate holding iff for `σ`, + the field `f` is visible in `Γ`. + +- `implemented(σ, τ)` be a predicate holding iff `σ` implements the trait `τ`. + +- `D` range over valid identifiers. + +- `G` denote the generic parameters on a data type definition. + +- `P` be all the product types in `σ`, namely: + + ```rust + P : struct D ; // Unit data types. + | struct D < G > ( σ_0, ..., σ_n ); // Tuple struct data types. + | struct D < G > { f_0: σ_0, ..., f_n: σ_n } // Named field struct data types. + | ( σ_0, ..., σ_n ) // Structural tuple types. + ; + ``` + +- `PG = { σ ∈ P | implemented(P, Drop) ≡ ⊥ ∧ non_exhaustive(σ) ≡ ⊥ }` + +- `p` denote a place expression. + +- `m(p)` be a predicate holding iff `p` is a mutable place. + +Given a place `p`, iff `typeof(p) ∈ PG`, then `p` may be *gradually* initialized. +This means that: + +1. an uninitialized field `f` of `p`, whether `f` be a numbered tuple + index or a named field, may be initialized with an assignment expression + `p.f = value_expr`, if `visible(typeof(p), f)`. + +2. iff `m(p)`, then `p.f` may be assigned to more than once. + Otherwise, it may only be assigned to once in any path in the control graph. + +3. a field or sub-field `f` of `p` may be moved out of + (unless `implemented(typeof(p), Drop)` or if `p` or `p.f` is borrowed) or + borrowed (including mutably iff `m(p)`) if `p.f` is definitely initialized. + +4. the place `p` is valid, meaning that it may be moved or referenced, + once all of its fields have been definitely initialized. + +5. before `p` goes out of scope, all fields of `p` must have been initialized + at some point at least once. # Drawbacks [drawbacks]: #drawbacks @@ -422,7 +579,7 @@ TODO TODO -# Future work -[future-work]: #future-work +# Future possibilities +[future-possibilities]: #future-possibilities TODO From fb68901fee370235e5f87138cc610dcbeb89a1d0 Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Mon, 29 Oct 2018 10:07:36 +0100 Subject: [PATCH 06/12] rfc, gradual-struct-init: initial work is done; pass bucket to Felix / Niko. --- text/0000-gradual-struct-init.md | 186 ++++++++++++++++++++++++++++--- 1 file changed, 170 insertions(+), 16 deletions(-) diff --git a/text/0000-gradual-struct-init.md b/text/0000-gradual-struct-init.md index ec4d13f094e..ea5d0c0d075 100644 --- a/text/0000-gradual-struct-init.md +++ b/text/0000-gradual-struct-init.md @@ -21,9 +21,53 @@ drop(pt); # Motivation [motivation]: #motivation -The main motivation of this RFC is +The RFC has two main motivations. +TODO: improve section prelude. -TODO +## Ergonomics and readability (TODO: concretize the title) + +TODO: Felix and/or Niko: please fill in this section :) + +## Immovable self-referential structs + +Currently, if you want to encode an immovable self-referential type, +you'll have to use `Cell`s and `Option`s such as with: + +```rust +#[derive(Debug)] +struct S<'a> { + val: u32, + ptr: Cell>, +} + +fn main() { + let s = S { val: 10, ptr: Cell::new(None), }; + s.ptr.set(Some(&s.val)); + + println!("s: {:?}", s); +} +``` + +With gradual initialization, we can instead allow you to write: + +```rust +#[derive(Debug)] +struct S<'a> { + val: u32, + ptr: &'a u32, +} + +fn main() { + let s: S; + s.val = 10; + s.ptr = &s.val; + + println!("s: {:?}", s); +} +``` + +You still cannot move `s` because `s.val` is borrowed in `s.ptr`; +However, the advantage here is that we avoid the indirection of `Option`. # Guide-level explanation [guide-level-explanation]: #guide-level-explanation @@ -229,7 +273,7 @@ The general condition here is that all fields must be *definitely initialized*. We previously noted that you may not move or reference parts of `foo` that are not yet initialized. The converse also applies. **_You may reference, move, -or copy parts the parts that are initialized while the whole type isn't._** (10): +or copy the parts that are initialized while the whole type isn't._** (10): ```rust let mut foo: Foo; @@ -248,14 +292,17 @@ foo.bar = 2; let my_mut_bar: &mut usize = &mut foo.bar; // OK! } // <- Mutable borrow ends here. -drop(foo.bar); // OK! +foo.qux = 1; ``` -Note in particular that `foo` was never fully initialized here. -As long as you don't move or borrow `foo`, this is permitted. -However, you must assign to all fields of `foo` at least once. -This restriction applies to make adding fields cause type errors -so that you can refactor more easily. +Note in particular that `foo.qux` is not initialized when we refer to `foo.bar`. +As long as you don't refer to `foo.qux` and `foo` while it is uninitialized this +is ok. You must also make sure that `foo` is definitively initialized at some +point even if you move out of some fields before `foo` goes out of scope. +Furthermore, it is not sufficient to simply initialize parts of `foo` at +various times; all of `foo` must be initialized at the *same* point. +These restriction apply to make adding fields cause type errors so that +you can refactor more easily. ### Immovable "self-referential" types @@ -533,6 +580,8 @@ Let: ; ``` + (If structural records were ever to be added they would also be included.) + - `PG = { σ ∈ P | implemented(P, Drop) ≡ ⊥ ∧ non_exhaustive(σ) ≡ ⊥ }` - `p` denote a place expression. @@ -554,32 +603,137 @@ This means that: borrowed (including mutably iff `m(p)`) if `p.f` is definitely initialized. 4. the place `p` is valid, meaning that it may be moved or referenced, - once all of its fields have been definitely initialized. + once all of its fields have been definitely initialized. -5. before `p` goes out of scope, all fields of `p` must have been initialized +5. before `p` goes out of scope, `p` must have been definitively initialized at some point at least once. # Drawbacks [drawbacks]: #drawbacks -TODO +The usual drawbacks with respect to having more ways to do it applies, +in particular, it could lead to decision fatigue between the struct literal +approach and the gradualist approach. + +Another drawback is that we don't provide an similar way for enums to be +gradually constructed. + +Furthermore, the gradual approach could be considered more imperative than +functional which some functional programmers may not prefer and this would +increase the likelihood of that in the ecosystem. However, the author of +this proposal is a functional programmer (of the Haskell variety) and they +do like what they are proposing. + +Yet another drawback is that it could become harder to see when a value is +constructed. + +Perhaps the most major drawback in this proposal is the complexity increase +in the type system. This will make alternative (to `rustc`) Rust compilers +more difficult to write. # Rationale and alternatives [rationale-and-alternatives]: #rationale-and-alternatives -TODO +With respect to the current design, the choice of surface syntax is quite +straightforward. We simply extend the assignment syntax that already exists. + +However, some tweaks in various directions could be made to the RFC: + +1. We could limit gradual initialization to top level bindings. + This would limit the power and is seemingly an arbitrary restriction + that could cause surprises. + +1. We could require that a gradually initialized place be definitively + initialized before allowing references or moving out of parts. + This would mean that we could not write: + + ```rust + let foo: Foo; + foo.bar = 1; + foo.baz = &foo.bar; + ``` + + That would remove one of the uniquely useful things about the + proposed syntax. + +3. We could limit this to nominal types or just to types with named fields. + While that is possible, it complicates understanding of the type system. + +4. We could allow `Drop` types to be gradually initialized. + This is discussed in the [unresolved questions][unresolved-questions]. # Prior art [prior-art]: #prior-art -TODO +None to our knowledge. # Unresolved questions [unresolved-questions]: #unresolved-questions -TODO +1. Should we lift the requirement that each field must be definitively + initialized at least once before a gradually initialized place goes + out of scope? + + We have imposed this restriction to make sure that adding a new field + causes a type error in all cases; if we lifted the restriction, + that would no longer hold. + +2. Should we allow gradual initialization for types implementing `Drop`? + + While this might make the language more consistent and uniform, + it might also become too much magic. In particular, it becomes less + clear when destructors will run and understanding when it happens might + require tracking "more boolean variables" in your head. + For example, if we write: + + ```rust + let foo; + foo.bar = ; + foo.baz = ; + foo.quux = ; + may_panic(); + + // a few lines later... + foo.wibble = ; + ``` + + Then we have to know that `foo` has 4 fields to know that `may_panic()` + will not run the destructor of `typeof(foo)`. + + It is however unclear if this would be a problem in practice and + it could be solved by privacy. + + If we do however allow `Drop` types to be gradually initialized, + then we should also lift the restriction on moving out of `Drop` + types to make things consistent. + + If we want to allow `Drop` types, then the restriction in 1. + is helpful because there's less to track in your head. + +3. If we permit structural records, and if the user writes: + + ```rust + let foo; + foo.bar = 1; + foo.baz = true; + foo.qux = "hello"; + drop(foo); + ``` + + Should then `foo`, if not otherwise constrained, be inferred to the type?: + + ```rust + { bar: i32, baz: bool, qux: &'static str } + ``` + + This could be quite ergonomic, but it could also be too much magic. # Future possibilities [future-possibilities]: #future-possibilities -TODO +[RFC 2534]: https://github.com/rust-lang/rfcs/pull/2534 + +Partially initialized objects is not exposed to the user in the type system. +We could allow this with `&uninit T` references. That would allow uninitialized +fields to pass into functions which can then initialize them. +See [RFC 2534] for a discussion on `&uninit T`. From 23d44e8a69c2f4dec2bda3eba5eb1ee3e6a13381 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20S=CC=B6c=CC=B6h=CC=B6n=CC=B6e=CC=B6i=CC=B6d=CC=B6?= =?UTF-8?q?e=CC=B6r=20Scherer?= Date: Fri, 2 Nov 2018 03:50:55 +0100 Subject: [PATCH 07/12] rfc, gradual-struct-init: fix typo. Co-Authored-By: Centril --- text/0000-gradual-struct-init.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-gradual-struct-init.md b/text/0000-gradual-struct-init.md index ea5d0c0d075..d1925a8a565 100644 --- a/text/0000-gradual-struct-init.md +++ b/text/0000-gradual-struct-init.md @@ -460,7 +460,7 @@ when the destructor will run, you would need to know that `SpecialDrop` has the fields `alpha`, `beta`, and `gamma` and that `gamma` hasn't been initialized yet. Thus, while it would be sound to allow it, if a type implements `Drop`, -the compiler will reject will not allow gradual initialization. +the compiler will reject gradual initialization. ### Tuples and tuple structs also work From 59415c0b81e0e780b2e0e663671965025f2ae640 Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Fri, 2 Nov 2018 04:00:17 +0100 Subject: [PATCH 08/12] gradual-struct-init: discuss gradual array init. --- text/0000-gradual-struct-init.md | 42 ++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/text/0000-gradual-struct-init.md b/text/0000-gradual-struct-init.md index d1925a8a565..060d43e22b2 100644 --- a/text/0000-gradual-struct-init.md +++ b/text/0000-gradual-struct-init.md @@ -731,9 +731,51 @@ None to our knowledge. # Future possibilities [future-possibilities]: #future-possibilities +## `&uninit T` + [RFC 2534]: https://github.com/rust-lang/rfcs/pull/2534 Partially initialized objects is not exposed to the user in the type system. We could allow this with `&uninit T` references. That would allow uninitialized fields to pass into functions which can then initialize them. See [RFC 2534] for a discussion on `&uninit T`. + +## Initialization arrays gradually + +A further extension to this RFC could be to permit the gradual initialization +of monomorphically sized arrays by using assignment to constant indices. +For example, we could allow: + +```rust +let arr: [i32; 3]; + +arr[0] = 0; +arr[1] = -12; +arr[2] = 42; + +drop(arr); +``` + +Here, the indices `0`, `1`, and `2` are all known at compile time and the +size of the array is also known. Thus, the compiler can know whether the +array has been definitively initialized and enforce that it is before the +array is used. The usual rules with respect to gradual initialization as +outlined in this RFC would otherwise apply. + +Similar to the type-defaulting idea for structural records, +we could also assume that `arr` is an array if the user writes: + +```rust +let arr; + +arr[0] = 0; +arr[1] = -12; +arr[2] = 42; + +drop(arr); +``` + +Since the idea of gradual array initialization is orthogonal and +because it overloads the meaning of the `place[idx] = `, +we leave this for future consideration and do not propose such a +facility at this time. From a9a7ec83e8b6ae4c1ba74a1983f63ac4c22f354f Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Fri, 2 Nov 2018 04:07:34 +0100 Subject: [PATCH 09/12] rfc, gradual-struct-init: clarify (9). --- text/0000-gradual-struct-init.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/text/0000-gradual-struct-init.md b/text/0000-gradual-struct-init.md index 060d43e22b2..cf1ce92a7bc 100644 --- a/text/0000-gradual-struct-init.md +++ b/text/0000-gradual-struct-init.md @@ -264,6 +264,9 @@ if random_bool() { // nothing here. } +// foo.qux = 2; -- adding this line would not help because now `foo.qux` may +// be initialized twice and `foo` is not a `mut` binding. + consume(foo); // ERROR! `foo.qux` not initialized in `else { .. }`. ``` From d9fab5fcea0cb0054f2818e7e106b166185222b4 Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Fri, 21 Dec 2018 23:01:53 +0100 Subject: [PATCH 10/12] rfc, gradual-struct-init: clarify definit init. --- text/0000-gradual-struct-init.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/text/0000-gradual-struct-init.md b/text/0000-gradual-struct-init.md index cf1ce92a7bc..8cffea08bd4 100644 --- a/text/0000-gradual-struct-init.md +++ b/text/0000-gradual-struct-init.md @@ -270,7 +270,8 @@ if random_bool() { consume(foo); // ERROR! `foo.qux` not initialized in `else { .. }`. ``` -The general condition here is that all fields must be *definitely initialized*. +The general condition here is that all fields must be *definitely initialized* +using the same logic for determining definite initialization as for local fields. ### Partial initialization and referencing fields @@ -611,6 +612,9 @@ This means that: 5. before `p` goes out of scope, `p` must have been definitively initialized at some point at least once. +In all cases, definitive initialization of fields are checked in the same +manner as for local fields prior to this RFC. + # Drawbacks [drawbacks]: #drawbacks @@ -738,7 +742,7 @@ None to our knowledge. [RFC 2534]: https://github.com/rust-lang/rfcs/pull/2534 -Partially initialized objects is not exposed to the user in the type system. +Partially initialized objects are not exposed to the user in the type system. We could allow this with `&uninit T` references. That would allow uninitialized fields to pass into functions which can then initialize them. See [RFC 2534] for a discussion on `&uninit T`. From b6d3e833504864a6e52f59b0ca2ca2b2403720b9 Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Fri, 21 Dec 2018 23:09:27 +0100 Subject: [PATCH 11/12] rfc, gradual-struct-init: elaborate on self-ref in summary. --- text/0000-gradual-struct-init.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/text/0000-gradual-struct-init.md b/text/0000-gradual-struct-init.md index 8cffea08bd4..75c3d7f9722 100644 --- a/text/0000-gradual-struct-init.md +++ b/text/0000-gradual-struct-init.md @@ -18,6 +18,20 @@ pt.y = 24; drop(pt); ``` +Additionally, initialized fields of a struct may be referenced by other fields +rendering the struct immovable. For example: + +```rust +struct Foo<'a> { a: u8, b: &'a u8 } + +let f; +f.a = 42; +f.b = &f.a; + +// drop(f); +// ------- not allowed since this moves `f` into `drop`. +``` + # Motivation [motivation]: #motivation From a1bda08c7a8ccec7b92aa770bb7a7b8b0d23096b Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Fri, 21 Dec 2018 23:20:34 +0100 Subject: [PATCH 12/12] rfc, gradual-struct-init: elaborate on Drop. --- text/0000-gradual-struct-init.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/text/0000-gradual-struct-init.md b/text/0000-gradual-struct-init.md index 75c3d7f9722..a776e342d9c 100644 --- a/text/0000-gradual-struct-init.md +++ b/text/0000-gradual-struct-init.md @@ -726,7 +726,9 @@ None to our knowledge. If we do however allow `Drop` types to be gradually initialized, then we should also lift the restriction on moving out of `Drop` - types to make things consistent. + types to make things consistent. If that restriction is not removed, + it would be to not allow gradual initialization of `Drop` types + as well for consistency. If we want to allow `Drop` types, then the restriction in 1. is helpful because there's less to track in your head.