From fbfecc2a40fc01bd27bd86a80db6ec8ae31517e3 Mon Sep 17 00:00:00 2001 From: Ozaren Date: Wed, 29 Aug 2018 11:03:25 -0700 Subject: [PATCH 01/13] Initial Commit for Write Pointer --- text/0000-write-pointers.md | 421 ++++++++++++++++++++++++++++++++++++ 1 file changed, 421 insertions(+) create mode 100644 text/0000-write-pointers.md diff --git a/text/0000-write-pointers.md b/text/0000-write-pointers.md new file mode 100644 index 00000000000..c9a32eca85d --- /dev/null +++ b/text/0000-write-pointers.md @@ -0,0 +1,421 @@ +- Feature Name: parital_initialization_and_write_pointers + +- Start Date: 2018-08-29 + +- RFC PR: (leave this empty) + +- Rust Issue: (leave this empty) + +# Summary + +[summary]: #summary + +This RFC aims to allow direct initialization for optimization, and partial struct and enum initialization, for ergonomics. It will do so through the usage of a two new reference type, `&out T` and `&uninit T` (the name is not important to me). + +# Motivation + +[motivation]: #motivation + +The builder pattern was created as a way to try and solve the issue of not having partial initialization, but it has problems with large structs, and that the `*Builder` struct must necessarily be larger than the target struct, null-ptr optimizations not-withstanding. Also, it is very expensive to move large structs, and relying on the optimizer to optimize out moves isn't very good, `&out T` could serve as a way to directly place things into the desired memory location and to maintain write-only memory. `&uninit T` will serve the purpose of partial initialization and direct initialization. + +# Guide-level explanation + +[guide-level-explanation]: #guide-level-explanation + +`&out T` is a write-only pointer to `T`, where `T: Copy`. The bound is necessary as it is not safe to overwrite `Drop` types without invoking the destructor. Does not run destructors on write. See [unresolved questions](#unresolved-questions) for a more in depth explanation. \ +`&uninit T` is a write-once pointer to `T`, after the first write, it is allowed to read the values (behaves exactly like `&mut T`). Does not run destructors on the first write. \ + +For all examples, I will use these two structs + +```Rust +struct Foo { a: u32, b: String, c: Bar } +#[derive(Clone, Copy)] +struct Bar { d: u32, e: f32 } + +impl std::ops::Drop for Foo { + fn drop(&mut T) { + println!("Dropping Foo {}", foo.b); + } +} +``` + +## `&uninit T` + +Using `&uninit T`, we can do partial initialization and directly initialize. +```Rust +let x: Foo; + +*(&uninit x.a) = 12; +*(&uninit x.b) = "Hello World".to_string(); +*(&uninit x.c.d) = 11; +*(&uninit x.c.e) = 10.0; +``` +This works because when we take an `&uninit` to `x.a`, we are implicity also taking an `&uninit` to `x`, and the dot operator will not attempt to read the memory location anywhere in `x`. + +For ease of use, you can simply write +```Rust +let x: Foo; + +x.a = 12; +x.b = "Hello World".to_string(); +x.c.d = 11; +x.c.e = 10.0; +``` +and the compiler will infer that all of these need to use `&uninit`, because `x` was not initialized directly. + +### Restrictions + +#### Storing + +You cannot store `&uninit T` in any way, not in structs, enums, unions, or behind any references. So all of these are invalid. + +```Rust +fn maybe_init(maybe: Option<&uninit T>) { ... } +fn init(ref_to_write: &mut &uninit T) { ... } +struct Temp { a: &uninit Foo } +``` + +#### Conditional Initialization + +One restriction to `&uninit T` is that we cannot conditionally initialize a value. For example, none of these are allowed. +```Rust +let x: Foo; +let condition = ...; + +if condition { + x.a = 12; // Error: Conditional partial initialization is not allowed +} +``` +```Rust +let x: Foo; +let condition = ...; + +while condition { + x.a = 12; // Error: Conditional partial initialization is not allowed +} +``` +```Rust +let x: Foo; + +for ... { + x.a = 12; // Error: Conditional partial initialization is not allowed +} +``` +Because if we do, then we can't gaurentee that the value is in fact initialized. + +Note, that this is not conditionally initializing `x.e`, because by the end of the `if-else` block, `x.e` is guaranteed to be initialized. +```Rust +let x: Bar; + +x.d = 10; + +if { ... any condition ... } { + x.e = 1.0; +} else { + x.e = 0.0; +} +``` + +### Using partially initialized variables + +```Rust +let x: Bar; +x.d = 2; + +// This is fine, we know that x.d is initialized +x.d.pow(4); +if x.d == 16 { + x.e = 10.0; +} else { + x.e = 0.0; +} +// This is fine, we know that x is initialized +assert_eq!(x.e, 10.0); +``` + +### Functions and closures + +You can accept `&uninit T` as arguments to a function or closure. + +```Rust +fn init_foo(foo: &uninit Foo) { ... } +let init_bar = |bar: &uninit Bar| { ... } +``` + +But if you do accept a `&uninit T` argument, you must write to it before returning from the function or closure. + +```Rust +fn valid_init_bar_v1(bar: &uninit Bar) { + bar.d = 10; + bar.e = 2.7182818; +} +fn valid_init_bar_v2(bar: &uninit Bar) { + // you must dereference if you write directly to a &uninit T + // This still does not drop the old value of bar + *bar = Bar { d: 10, e: 2.7182818 }; +} +fn invalid_init_bar_v1(bar: &uninit Bar) { + bar.d = 10; + // Error, bar is not completely initialized (Bar.e is not initialized) +} + +fn invalid_init_bar_v2(bar: &uninit Bar) { + bar.d = 10; + if bar.d == 9 { + return; // Error, bar is not completely initialized (Bar.e is not initialized) + } + bar.e = 10.0; +} +``` + +If a closure captures a `&uninit T`, then it becomes a `FnOnce`, because of the write semantics, the destructors will not be run the first time. + +```Rust +let x: Foo; + +let init = || x.a = 12; // init: FnOnce() -> () +``` + +**Note on Panicky Functions:** +If a function panics, then all fields initialized in that function will be dropped. No cross-function analysis will be done. + +## `&out T` + +Using `&out T`, we can directly initialize a value and guarantee to write only behavior. \ +That would add give a memory location to write to directly instead of relying on move-elimination optimizations. + +```Rust +use super::os::OsFrameBuffer; + +#[derive(Copy)] +struct Rgb(pub u8, pub u8, pub u8); +/// This abstraction that exposes a Frame Buffer allocated by the OS, and is unsafe to read from +struct FrameBuffer( OsFrameBuffer ); + +impl FrameBuffer { + fn new(&uninit self) { + self.0.init() // initialize frame buffer + } + + fn write_to_pixel(&mut self, row: usize, col: usize) -> &out Rgb { + self.0.get_out(row, col) + } +} +``` +This could be used like this +```Rust +let buffer; +FrameBuffer::new(&uninit buffer); + +*buffer.write(0, 0) = Rgb(50, 50, 255); +*buffer.write(10, 20) = Rgb(0, 250, 25); +/// ... +``` + +## Constructors and Direct Initialization + +Using `&uninit` we can create constructors for Rust! +```Rust +struct Rgb(u8, u8 ,u8); + +impl Rgb { + fn init(&uninit self, r: u8, g: u8, b: u8) { + self.0 = r; + self.1 = g; + self.2 = b; + } +} + +let color: Rgb; +color.init(20, 23, 255); +``` + +and we can do direct initialization +```Rust +impl Vec { + pub fn emplace_back(&mut self) -> &uninit T { + ... // magic to allocate space and create pointer + } +} +``` + +and maintain write-only buffers +```Rust +struct WriteOnly([u8; 1024]); + +impl WriteOnly { + pub fn write(&out self, byte: u8, location: usize) { + self.0[location] = byte; // currently not possible to index like this, but we could imagine a IndexOut, that will handle this case + } +} +``` + +# Reference-level explanation + +[reference-level-explanation]: #reference-level-explanation + +**NOTE** This RFC does NOT aim to create new raw pointer types, so no `*out T` or `*uninit T`. There is no point in creating these. + +## Rules of `&uninit T` + +`&uninit T` should follow some rules in so that is is easy to reason about `&uninit T` locally and maintain soundness +- `&uninit T` follows the same rules as `&mut T` for the borrow checker +- `&uninit T` can only be assigned to once + - After being written to `&uninit T` are promoted to a `&mut T` +- Writing does not drop old value. + - Otherwise, it would not handle writing to uninitialized memory + - More importantly, dropping requires at least one read, which is not possible with a write-only pointer +- You cannot reference partially initialized memory +```Rust +let x: Bar; + +fn init_bar(bar: &uninit Bar) { ... } +fn init_u32(x: &uninit u32) { ... } + +x.e = 10.0; + +// init_bar(&uninit x); // compile time error: attempting to reference partially initialized memory +init_u32(&uninit x.d); // fine, x.d is completely uninitialized. +``` +- Functions and closures that take a `&uninit T` argument must initialize it before returning + - You cannot return an `&uninit T` +- You can take a `&uninit T` on any `T` that represents uninitialized memory, for example: only the first is ok. +```Rust +let x: Foo; +let y = &uninit x; +``` +```Rust +let x: Foo = Foo { a: 12, b: "Hello World".to_string() }; +init(a: &uninit Foo) { ... } +init(&uninit x); // this function will overwrite, but not drop to the old value of x, so this is a compile-time error +``` + +## Rules of `&out T` + +`&out T` should follow some rules in so that is is easy to reason about `&out T` locally and maintain soundness +- `&out T` follows the same rules as `&mut T` for the borrow checker +- Writing does not drop old value. + - Dropping requires at least one read, which is not possible with a write-only pointer +- You can take a `&out T` on any `T: Copy` + - because destructors are never run on write, `T: Copy` is necessary to guarantee no custom destructors. This bound can be changed once negative trait bounds land, then we can have `T: !Drop`. Changing from `T: Copy` to `T: !Drop` will be backwards compatible, so we can move forward with just a `T: Copy` bound for now. + +## Coercion Rules + +`&T` - (none) // no change \ +`&mut T` - `&T`, `&out T` if, `T: Copy` \ +`&out T` - (none) // for similar reasons to why `&T` does not coerce \ +`&uninit T` - `&out T` if `T: Copy` and `&T` or `&mut T` once initialized. + +## `self` + +We will add `&uninit self` and `&out self` as sugar for `self: &uninit Self` and `self: &out Self` respectively. This is for consistency with `&self`, and `&mut self` + +## Panicky functions in detail + +Because we can pass `&uninit T` and `&out T` to functions, we must consider what happens if a function panics. For example: +```Rust +fn init_foo_can_panic(foo: &uninit Foo) { + foo.b = "Hello World".to_string(); + foo.a = 12; + + if foo.a == 12 { + // When we panic here, we should drop all values that are initialized in the function. + // Nothing could have been initialized before the function because we have a &uninit T + panic!("Oh no, something went wrong!"); + } + + foo.c = Bar { d = 10, e = 12.0 }; +} + +fn out_bar_panics(foo: &out Bar) { + // When we panic here we drop here we don't ever drop any value behind a &out because &out can never have a destructor, it doesn't matter + panic!("Oh no, something went wrong!"); +} + +let x: Foo; + +init_foo_can_panic(&uninit x); + +let x: Bar; + +out_bar_panics(&out x); // when we take a &out, we are asserting that the old value doesn't need to drop, and doesn't matter. This is fine because Bar is Copy and does not have a destructor. +``` + +# Drawbacks + +[drawbacks]: #drawbacks + + - This is a significant change to the language and introduces a lot of complexity. \ + - Partial initialization can be solved entirely through the type-system as shown [here](https://scottjmaddox.github.io/Safe-partial-initialization-in-Rust/). But this does have its problems, such as requiring an unstable feature (untagged_unions) or increased size of the uninitialized value (using enums). + +# Rationale and alternatives + +[rationale-and-alternatives]: #rationale-and-alternatives + +## `T: !Drop` for `&out T` + +Once negative trait bounds become stable, the bounds for `&out T` will change to `T: !Drop`. But this does not seem like the correct bound, see [here](#unresolved-questions) for why. + +## Allow Drop types to be partially initialized + +Then they would only be dropped if all of their fields are initialized + +## Placement-new + +Placement new would help, with initializing large structs. + +## As sugar + +This could be implemented as sugar, where all fields of structs that are partially initialized are turned into temp-variables that are then passed through the normal pipeline. + +For example +```Rust +let x: Bar; +x.d = 10; +x.e = 12.0; +``` + +would desugar to + +```Rust +let x: Bar; +let xd = 10; +let xe = 12.0; +x = Bar { d: xd, e: xe }; +``` + +But this would not be able to replace placement new as it can't handle `&uninit T` through function boundaries. Also this would not solve the problem of direct-initialization. + +# Prior art +[prior-art]: #prior-art + +Out pointers in C++, (not exactly the same, but similar idea) + +`&out T` in C# + +# Unresolved questions +[unresolved-questions]: #unresolved-questions + + - What is the correct bounds for `T` in `&out T`? conservatively, `T: Copy` works and is sound. But as [@TechnoMancer](https://internals.rust-lang.org/u/TechnoMancer) pointed out, `T: !Drop` is not the correct bound. For example, `Wrapper(Vec)`, clearly cannot be overwritten safely, because `Vec`, must drop do deallocate the `Vec`, but `Wrapper` itself does not implement drop. Therefore either a new trait is needed (but unwanted), or we must keep the `Copy` bound. + + +--- + +edit: +Added Panicky Function sub-section due to [@rkruppe](https://internals.rust-lang.org/u/rkruppe)'s insights + +added `&out T` by C# to prior arts and alternative syntax due to [@earthengine](https://internals.rust-lang.org/u/earthengine)'s suggestion + +removed lots of unnecessary spaces and newlines + +edit 2: + +Incorporating [@gbutler](https://internals.rust-lang.org/u/gbutler)'s proposal of splitting `&uninit T` into `&out T` and `&uninit T` + +edit 3: + +Used [@gbutler](https://internals.rust-lang.org/u/gbutler)'s example of FrameBuffer that interfaces hardware for `&out T` + +--- +I would like to thank all the people who helped refine this proposal to its current state: [@rkruppe](https://internals.rust-lang.org/u/rkruppe), [@earthengine](https://internals.rust-lang.org/u/earthengine), [@gbutler](https://internals.rust-lang.org/u/gbutler), +and [@TechnoMancer](https://internals.rust-lang.org/u/TechnoMancer) thank you! \ No newline at end of file From b09b4498fbc34d2a69f37f67d2734a64db03044e Mon Sep 17 00:00:00 2001 From: Ozaren Date: Wed, 29 Aug 2018 14:15:50 -0700 Subject: [PATCH 02/13] fixed example for &out T --- text/0000-write-pointers.md | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/text/0000-write-pointers.md b/text/0000-write-pointers.md index c9a32eca85d..754ce7da9c4 100644 --- a/text/0000-write-pointers.md +++ b/text/0000-write-pointers.md @@ -1,4 +1,4 @@ -- Feature Name: parital_initialization_and_write_pointers +- Feature Name: parital_initialization_and_write_references - Start Date: 2018-08-29 @@ -22,8 +22,8 @@ The builder pattern was created as a way to try and solve the issue of not havin [guide-level-explanation]: #guide-level-explanation -`&out T` is a write-only pointer to `T`, where `T: Copy`. The bound is necessary as it is not safe to overwrite `Drop` types without invoking the destructor. Does not run destructors on write. See [unresolved questions](#unresolved-questions) for a more in depth explanation. \ -`&uninit T` is a write-once pointer to `T`, after the first write, it is allowed to read the values (behaves exactly like `&mut T`). Does not run destructors on the first write. \ +`&out T` is a write-only reference to `T`, where `T: Copy`. The bound is necessary as it is not safe to overwrite `Drop` types without invoking the destructor. Does not run destructors on write. See [unresolved questions](#unresolved-questions) for a more in depth explanation. \ +`&uninit T` is a write-once reference to `T`, after the first write, it is allowed to read the values (behaves exactly like `&mut T`). Does not run destructors on the first write. \ For all examples, I will use these two structs @@ -185,20 +185,18 @@ Using `&out T`, we can directly initialize a value and guarantee to write only b That would add give a memory location to write to directly instead of relying on move-elimination optimizations. ```Rust -use super::os::OsFrameBuffer; - -#[derive(Copy)] +#[derive(Clone, Copy)] struct Rgb(pub u8, pub u8, pub u8); /// This abstraction that exposes a Frame Buffer allocated by the OS, and is unsafe to read from -struct FrameBuffer( OsFrameBuffer ); +struct FrameBuffer( ... ); impl FrameBuffer { - fn new(&uninit self) { - self.0.init() // initialize frame buffer - } + /// initializes the FrameBuffer in place + fn new(&uninit self) { ... } + /// gets a write only refernce to pixel at position (row, col) fn write_to_pixel(&mut self, row: usize, col: usize) -> &out Rgb { - self.0.get_out(row, col) + ... } } ``` @@ -207,11 +205,13 @@ This could be used like this let buffer; FrameBuffer::new(&uninit buffer); -*buffer.write(0, 0) = Rgb(50, 50, 255); -*buffer.write(10, 20) = Rgb(0, 250, 25); +*buffer.write_to_pixel(0, 0) = Rgb(50, 50, 255); +*buffer.write_to_pixel(10, 20) = Rgb(0, 250, 25); /// ... ``` +**Note:** `Rgb` is `Copy`, if it wasn't we could not gaurentee that we can safely overwrite it + ## Constructors and Direct Initialization Using `&uninit` we can create constructors for Rust! @@ -234,7 +234,7 @@ and we can do direct initialization ```Rust impl Vec { pub fn emplace_back(&mut self) -> &uninit T { - ... // magic to allocate space and create pointer + ... // magic to allocate space and create reference } } ``` @@ -264,7 +264,7 @@ impl WriteOnly { - After being written to `&uninit T` are promoted to a `&mut T` - Writing does not drop old value. - Otherwise, it would not handle writing to uninitialized memory - - More importantly, dropping requires at least one read, which is not possible with a write-only pointer + - More importantly, dropping requires at least one read, which is not possible with a write-only reference - You cannot reference partially initialized memory ```Rust let x: Bar; @@ -295,7 +295,7 @@ init(&uninit x); // this function will overwrite, but not drop to the old value `&out T` should follow some rules in so that is is easy to reason about `&out T` locally and maintain soundness - `&out T` follows the same rules as `&mut T` for the borrow checker - Writing does not drop old value. - - Dropping requires at least one read, which is not possible with a write-only pointer + - Dropping requires at least one read, which is not possible with a write-only reference - You can take a `&out T` on any `T: Copy` - because destructors are never run on write, `T: Copy` is necessary to guarantee no custom destructors. This bound can be changed once negative trait bounds land, then we can have `T: !Drop`. Changing from `T: Copy` to `T: !Drop` will be backwards compatible, so we can move forward with just a `T: Copy` bound for now. @@ -416,6 +416,10 @@ edit 3: Used [@gbutler](https://internals.rust-lang.org/u/gbutler)'s example of FrameBuffer that interfaces hardware for `&out T` +edit 4: + +Fixed example for `&out T`. + --- I would like to thank all the people who helped refine this proposal to its current state: [@rkruppe](https://internals.rust-lang.org/u/rkruppe), [@earthengine](https://internals.rust-lang.org/u/earthengine), [@gbutler](https://internals.rust-lang.org/u/gbutler), and [@TechnoMancer](https://internals.rust-lang.org/u/TechnoMancer) thank you! \ No newline at end of file From 0c86e89678ec24d94fd395f101d427d3911b0d15 Mon Sep 17 00:00:00 2001 From: Ozaren Date: Thu, 30 Aug 2018 06:36:02 -0700 Subject: [PATCH 03/13] Added casting rules, from write references to raw pointers --- text/0000-write-pointers.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/text/0000-write-pointers.md b/text/0000-write-pointers.md index 754ce7da9c4..3e2b0c851a7 100644 --- a/text/0000-write-pointers.md +++ b/text/0000-write-pointers.md @@ -306,6 +306,11 @@ init(&uninit x); // this function will overwrite, but not drop to the old value `&out T` - (none) // for similar reasons to why `&T` does not coerce \ `&uninit T` - `&out T` if `T: Copy` and `&T` or `&mut T` once initialized. +## Casting Rules + +`*const T` - // no change \ +`*mut T` - `&mut T`, `&out T`, `&uninit T` + ## `self` We will add `&uninit self` and `&out self` as sugar for `self: &uninit Self` and `self: &out Self` respectively. This is for consistency with `&self`, and `&mut self` @@ -420,6 +425,10 @@ edit 4: Fixed example for `&out T`. +edit 5: + +Added casting rules from `&out T` and `&uninit T` to raw pointers. + --- I would like to thank all the people who helped refine this proposal to its current state: [@rkruppe](https://internals.rust-lang.org/u/rkruppe), [@earthengine](https://internals.rust-lang.org/u/earthengine), [@gbutler](https://internals.rust-lang.org/u/gbutler), and [@TechnoMancer](https://internals.rust-lang.org/u/TechnoMancer) thank you! \ No newline at end of file From 1a4c8900f10edc136610afc87976478492679284 Mon Sep 17 00:00:00 2001 From: Ozaren Date: Thu, 30 Aug 2018 16:00:43 -0700 Subject: [PATCH 04/13] added unresolved questions should raw pointers, *out T be added should a new trait, OverwriteSafe, be used for the bounds of &out T --- text/0000-write-pointers.md | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/text/0000-write-pointers.md b/text/0000-write-pointers.md index 3e2b0c851a7..49cb43e3074 100644 --- a/text/0000-write-pointers.md +++ b/text/0000-write-pointers.md @@ -301,10 +301,15 @@ init(&uninit x); // this function will overwrite, but not drop to the old value ## Coercion Rules -`&T` - (none) // no change \ -`&mut T` - `&T`, `&out T` if, `T: Copy` \ -`&out T` - (none) // for similar reasons to why `&T` does not coerce \ -`&uninit T` - `&out T` if `T: Copy` and `&T` or `&mut T` once initialized. + - `&T` // no change + - `*const T` + - `&mut T` + - `*mut T` + - `&T` + - `&out T` if, `T: Copy` + - `&out T` + - `*mut T` // for similar reasons to why `&T` only coerces to `*const T` + - `&uninit T` - `*mut T`, `&out T` if `T: Copy` and `&T` or `&mut T` once initialized. ## Casting Rules @@ -402,8 +407,12 @@ Out pointers in C++, (not exactly the same, but similar idea) [unresolved-questions]: #unresolved-questions - What is the correct bounds for `T` in `&out T`? conservatively, `T: Copy` works and is sound. But as [@TechnoMancer](https://internals.rust-lang.org/u/TechnoMancer) pointed out, `T: !Drop` is not the correct bound. For example, `Wrapper(Vec)`, clearly cannot be overwritten safely, because `Vec`, must drop do deallocate the `Vec`, but `Wrapper` itself does not implement drop. Therefore either a new trait is needed (but unwanted), or we must keep the `Copy` bound. - - + - Should we introduce a `*out T` raw pointer, that does not drop its value upon write, for all `T: Copy`. + - `&out T` coerces to `*out T` + - no reading from `*out T` + - Should we introduce a trait `OverwriteSafe`, which is auto-implemented (like `Send` and `Sync`) where all types `T` are `OverwriteSafe` unless, and use this as the bound for `&out T` + - `T: Drop` + - one of `T`'s fields is not `OverwriteSafe` --- edit: From 9925a94ed069ecee3df5df0e335ee1387faadb20 Mon Sep 17 00:00:00 2001 From: Ozaren Date: Tue, 11 Sep 2018 08:36:56 -0700 Subject: [PATCH 05/13] Clarified Coercion Rules around &uninit T Specified how coercion rules around variables that are defined as immutable/mutable should be handled. i.e. let foo: Foo; vs let mut foo: Foo; --- text/0000-write-pointers.md | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/text/0000-write-pointers.md b/text/0000-write-pointers.md index 49cb43e3074..81ec8d95bba 100644 --- a/text/0000-write-pointers.md +++ b/text/0000-write-pointers.md @@ -309,7 +309,21 @@ init(&uninit x); // this function will overwrite, but not drop to the old value - `&out T` if, `T: Copy` - `&out T` - `*mut T` // for similar reasons to why `&T` only coerces to `*const T` - - `&uninit T` - `*mut T`, `&out T` if `T: Copy` and `&T` or `&mut T` once initialized. + - `&uninit T` - `*mut T`, `&out T` if `T: Copy` and `&T` or `&mut T` once initialized depending on if the variable is mutable or not. + ```Rust + struct Foo(i32, i32); + let foo: Foo; + foo.0 = 10; + foo.1 = 20; + + // foo.0 = 10; // error foo is immutable + + let mut foo_mut: Foo; + foo_mut.0 = 10; + foo_mut.1 = 20; + + foo_mut.0 = 10; // fine, foo_mut is mutable + ``` ## Casting Rules From d7b9e1397f2b4851828c0937c4e4ad07ffe4d693 Mon Sep 17 00:00:00 2001 From: Ozaren Date: Tue, 11 Sep 2018 12:35:04 -0700 Subject: [PATCH 06/13] Added constraint to &uninit T Due to insights of @TechnoMancer, I added a sub-constraint to &uninit _ that makes it impossible for any type parameter to resolve to &uninit _ --- text/0000-write-pointers.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/text/0000-write-pointers.md b/text/0000-write-pointers.md index 81ec8d95bba..99750f0fa72 100644 --- a/text/0000-write-pointers.md +++ b/text/0000-write-pointers.md @@ -279,6 +279,7 @@ init_u32(&uninit x.d); // fine, x.d is completely uninitialized. ``` - Functions and closures that take a `&uninit T` argument must initialize it before returning - You cannot return an `&uninit T` + - because of this rule, `T` cannot resolve to `&uninit _` in any case, because it is impossible to parametrically initialize the `&uninit` - You can take a `&uninit T` on any `T` that represents uninitialized memory, for example: only the first is ok. ```Rust let x: Foo; @@ -452,6 +453,11 @@ edit 5: Added casting rules from `&out T` and `&uninit T` to raw pointers. +edit 6: + +added the constraint that no type parameter `T` can resolve to `&uninit _`, due to [@ExpHP](https://github.com/ExpHP)'s insights + --- I would like to thank all the people who helped refine this proposal to its current state: [@rkruppe](https://internals.rust-lang.org/u/rkruppe), [@earthengine](https://internals.rust-lang.org/u/earthengine), [@gbutler](https://internals.rust-lang.org/u/gbutler), -and [@TechnoMancer](https://internals.rust-lang.org/u/TechnoMancer) thank you! \ No newline at end of file +and [@TechnoMancer](https://internals.rust-lang.org/u/TechnoMancer) in the Pre-RFC, and +[@cramertj](https://github.com/cramertj), [@ExpHP](https://github.com/ExpHP) in the RFC thank you! From a84c6cebc6f4340d887ec1114062ae8a17951c0f Mon Sep 17 00:00:00 2001 From: Ozaren Date: Fri, 12 Oct 2018 12:51:47 -0700 Subject: [PATCH 07/13] Changes as a result of @matthewjasper's review Fixed problems listed by @matthewjasper in his review on 2018 October 12 Removed comment about `!Drop` bound for `&out T`, because it is incorrect. Removed the Casting Rules Section --- text/0000-write-pointers.md | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/text/0000-write-pointers.md b/text/0000-write-pointers.md index 99750f0fa72..a642520d2c3 100644 --- a/text/0000-write-pointers.md +++ b/text/0000-write-pointers.md @@ -259,9 +259,8 @@ impl WriteOnly { ## Rules of `&uninit T` `&uninit T` should follow some rules in so that is is easy to reason about `&uninit T` locally and maintain soundness -- `&uninit T` follows the same rules as `&mut T` for the borrow checker +- `&uninit T` should be an exclusive pointer, similar to how `&mut T` is an exclusive pointer - `&uninit T` can only be assigned to once - - After being written to `&uninit T` are promoted to a `&mut T` - Writing does not drop old value. - Otherwise, it would not handle writing to uninitialized memory - More importantly, dropping requires at least one read, which is not possible with a write-only reference @@ -278,7 +277,6 @@ x.e = 10.0; init_u32(&uninit x.d); // fine, x.d is completely uninitialized. ``` - Functions and closures that take a `&uninit T` argument must initialize it before returning - - You cannot return an `&uninit T` - because of this rule, `T` cannot resolve to `&uninit _` in any case, because it is impossible to parametrically initialize the `&uninit` - You can take a `&uninit T` on any `T` that represents uninitialized memory, for example: only the first is ok. ```Rust @@ -298,7 +296,7 @@ init(&uninit x); // this function will overwrite, but not drop to the old value - Writing does not drop old value. - Dropping requires at least one read, which is not possible with a write-only reference - You can take a `&out T` on any `T: Copy` - - because destructors are never run on write, `T: Copy` is necessary to guarantee no custom destructors. This bound can be changed once negative trait bounds land, then we can have `T: !Drop`. Changing from `T: Copy` to `T: !Drop` will be backwards compatible, so we can move forward with just a `T: Copy` bound for now. + - because destructors are never run on write, `T: Copy` is necessary to guarantee no custom destructors. ## Coercion Rules @@ -326,11 +324,6 @@ init(&uninit x); // this function will overwrite, but not drop to the old value foo_mut.0 = 10; // fine, foo_mut is mutable ``` -## Casting Rules - -`*const T` - // no change \ -`*mut T` - `&mut T`, `&out T`, `&uninit T` - ## `self` We will add `&uninit self` and `&out self` as sugar for `self: &uninit Self` and `self: &out Self` respectively. This is for consistency with `&self`, and `&mut self` @@ -416,12 +409,13 @@ But this would not be able to replace placement new as it can't handle `&uninit Out pointers in C++, (not exactly the same, but similar idea) -`&out T` in C# +`out T` in C# # Unresolved questions [unresolved-questions]: #unresolved-questions - What is the correct bounds for `T` in `&out T`? conservatively, `T: Copy` works and is sound. But as [@TechnoMancer](https://internals.rust-lang.org/u/TechnoMancer) pointed out, `T: !Drop` is not the correct bound. For example, `Wrapper(Vec)`, clearly cannot be overwritten safely, because `Vec`, must drop do deallocate the `Vec`, but `Wrapper` itself does not implement drop. Therefore either a new trait is needed (but unwanted), or we must keep the `Copy` bound. + - On this note should we even have a bound on `T` for `&out T`, it does not break memory safety to remove the `Copy` bound, but may cause logic errors. So this error could be demoted to an error-by-default-lint instead. - Should we introduce a `*out T` raw pointer, that does not drop its value upon write, for all `T: Copy`. - `&out T` coerces to `*out T` - no reading from `*out T` @@ -457,7 +451,15 @@ edit 6: added the constraint that no type parameter `T` can resolve to `&uninit _`, due to [@ExpHP](https://github.com/ExpHP)'s insights +edit 7: + +Fixed problems listed by [@matthewjasper](https://github.com/matthewjasper) in the [review](https://github.com/rust-lang/rfcs/pull/2534/files/d7b9e1397f2b4851828c0937c4e4ad07ffe4d693) + +Removed comment about `!Drop` bound for `&out T`, because it is incorrect. + +Removed the Casting Rules Section, because it was wrong, instead to convert raw pointers to references, use a reborrow. for example if `x: *mut T`, then `unsafe { &out *x }` will turn it into an `&out T`. + --- I would like to thank all the people who helped refine this proposal to its current state: [@rkruppe](https://internals.rust-lang.org/u/rkruppe), [@earthengine](https://internals.rust-lang.org/u/earthengine), [@gbutler](https://internals.rust-lang.org/u/gbutler), and [@TechnoMancer](https://internals.rust-lang.org/u/TechnoMancer) in the Pre-RFC, and -[@cramertj](https://github.com/cramertj), [@ExpHP](https://github.com/ExpHP) in the RFC thank you! +[@cramertj](https://github.com/cramertj), [@ExpHP](https://github.com/ExpHP), and [@matthewjasper](https://github.com/matthewjasper) in the RFC thank you! From 5845403a17c9ca7ec40683287e57ffae75f9bd1c Mon Sep 17 00:00:00 2001 From: Ozaren Date: Fri, 12 Oct 2018 12:56:32 -0700 Subject: [PATCH 08/13] Missed fix, extension of last commit --- text/0000-write-pointers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-write-pointers.md b/text/0000-write-pointers.md index a642520d2c3..c263dff2a44 100644 --- a/text/0000-write-pointers.md +++ b/text/0000-write-pointers.md @@ -372,7 +372,7 @@ out_bar_panics(&out x); // when we take a &out, we are asserting that the old va ## `T: !Drop` for `&out T` -Once negative trait bounds become stable, the bounds for `&out T` will change to `T: !Drop`. But this does not seem like the correct bound, see [here](#unresolved-questions) for why. +If negative trait bounds become stable, the bounds for `&out T` will change to `T: !Drop`. But this does not seem like the correct bound, see [here](#unresolved-questions) for why. ## Allow Drop types to be partially initialized From 0ce396df692ea0e978c156139989ac55748c1314 Mon Sep 17 00:00:00 2001 From: Ozaren Date: Fri, 12 Oct 2018 13:10:08 -0700 Subject: [PATCH 09/13] Added an unresolved question tl;dr should we use drop flags for conditional initialization --- text/0000-write-pointers.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/text/0000-write-pointers.md b/text/0000-write-pointers.md index c263dff2a44..eac3aa4ef51 100644 --- a/text/0000-write-pointers.md +++ b/text/0000-write-pointers.md @@ -422,6 +422,7 @@ Out pointers in C++, (not exactly the same, but similar idea) - Should we introduce a trait `OverwriteSafe`, which is auto-implemented (like `Send` and `Sync`) where all types `T` are `OverwriteSafe` unless, and use this as the bound for `&out T` - `T: Drop` - one of `T`'s fields is not `OverwriteSafe` + - We can use drop flags to check when a variable has been initialized, and use that information to allow conditional initialization. But should we do this? --- edit: @@ -459,6 +460,8 @@ Removed comment about `!Drop` bound for `&out T`, because it is incorrect. Removed the Casting Rules Section, because it was wrong, instead to convert raw pointers to references, use a reborrow. for example if `x: *mut T`, then `unsafe { &out *x }` will turn it into an `&out T`. +Added an unresolved question, about use of drop flags to allow for conditional initialization. + --- I would like to thank all the people who helped refine this proposal to its current state: [@rkruppe](https://internals.rust-lang.org/u/rkruppe), [@earthengine](https://internals.rust-lang.org/u/earthengine), [@gbutler](https://internals.rust-lang.org/u/gbutler), and [@TechnoMancer](https://internals.rust-lang.org/u/TechnoMancer) in the Pre-RFC, and From 68a8630435c71353f1b1cbd5d22971dc6271a2b2 Mon Sep 17 00:00:00 2001 From: Ozaren Date: Fri, 12 Oct 2018 16:51:58 -0700 Subject: [PATCH 10/13] changed two variables to be mut, because they could be changed in a loop --- text/0000-write-pointers.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/0000-write-pointers.md b/text/0000-write-pointers.md index eac3aa4ef51..2e297592d43 100644 --- a/text/0000-write-pointers.md +++ b/text/0000-write-pointers.md @@ -87,7 +87,7 @@ if condition { } ``` ```Rust -let x: Foo; +let mut x: Foo; let condition = ...; while condition { @@ -95,7 +95,7 @@ while condition { } ``` ```Rust -let x: Foo; +let mut x: Foo; for ... { x.a = 12; // Error: Conditional partial initialization is not allowed From be86407020f5c5a45da28ac3a4a28d8199b286d9 Mon Sep 17 00:00:00 2001 From: Ozaren Date: Tue, 16 Oct 2018 10:26:53 -0700 Subject: [PATCH 11/13] Updated emplace_back implementation --- text/0000-write-pointers.md | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/text/0000-write-pointers.md b/text/0000-write-pointers.md index 2e297592d43..c4de5c8170e 100644 --- a/text/0000-write-pointers.md +++ b/text/0000-write-pointers.md @@ -230,11 +230,20 @@ let color: Rgb; color.init(20, 23, 255); ``` -and we can do direct initialization +and we can do direct initialization (also called Placement New). ```Rust impl Vec { - pub fn emplace_back(&mut self) -> &uninit T { - ... // magic to allocate space and create reference + pub fn emplace_back(&mut self, init: impl FnOnce(&uninit T)) { + /// This code is taken from the Vec push implementation is the std lib + /// and adapted to use &uninit to show how it will be used for placement new + if self.len == self.buf.cap() { + self.reserve(1); + } + unsafe { + let end: &uninit T = &uninit *self.as_mut_ptr().add(self.len); + init(end); // this line has been changed for the purposes of placement new + self.len += 1; + } } } ``` @@ -462,6 +471,10 @@ Removed the Casting Rules Section, because it was wrong, instead to convert raw Added an unresolved question, about use of drop flags to allow for conditional initialization. +edit 8: + +Updated emplace_back implementation with one similar to `Vec::push` (and taken and adapted from`Vec::push`). + --- I would like to thank all the people who helped refine this proposal to its current state: [@rkruppe](https://internals.rust-lang.org/u/rkruppe), [@earthengine](https://internals.rust-lang.org/u/earthengine), [@gbutler](https://internals.rust-lang.org/u/gbutler), and [@TechnoMancer](https://internals.rust-lang.org/u/TechnoMancer) in the Pre-RFC, and From 01e8e036e809428088483f5ad8acd571b4ef0779 Mon Sep 17 00:00:00 2001 From: Ozaren Date: Tue, 16 Oct 2018 10:32:15 -0700 Subject: [PATCH 12/13] Made an `&out` example better --- text/0000-write-pointers.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/text/0000-write-pointers.md b/text/0000-write-pointers.md index c4de5c8170e..6c9e7f80564 100644 --- a/text/0000-write-pointers.md +++ b/text/0000-write-pointers.md @@ -208,6 +208,7 @@ FrameBuffer::new(&uninit buffer); *buffer.write_to_pixel(0, 0) = Rgb(50, 50, 255); *buffer.write_to_pixel(10, 20) = Rgb(0, 250, 25); /// ... +// println!("{}", buffer.write_to_pixel(0, 0).0); // This would be a compile time error: cannot read from write-only borrowed content ``` **Note:** `Rgb` is `Copy`, if it wasn't we could not gaurentee that we can safely overwrite it @@ -475,6 +476,8 @@ edit 8: Updated emplace_back implementation with one similar to `Vec::push` (and taken and adapted from`Vec::push`). +Made one `&out` example better by adding where a compile time error would be if `&out` is used incorretly. + --- I would like to thank all the people who helped refine this proposal to its current state: [@rkruppe](https://internals.rust-lang.org/u/rkruppe), [@earthengine](https://internals.rust-lang.org/u/earthengine), [@gbutler](https://internals.rust-lang.org/u/gbutler), and [@TechnoMancer](https://internals.rust-lang.org/u/TechnoMancer) in the Pre-RFC, and From 80d162d284b6feb9feea765533153dd7eb36909c Mon Sep 17 00:00:00 2001 From: Ozaren Date: Fri, 11 Jan 2019 22:55:16 -0700 Subject: [PATCH 13/13] Removed &out, as it is completely unnecessary. --- text/0000-write-pointers.md | 107 ++++++------------------------------ 1 file changed, 18 insertions(+), 89 deletions(-) diff --git a/text/0000-write-pointers.md b/text/0000-write-pointers.md index 2e297592d43..fd4375288e6 100644 --- a/text/0000-write-pointers.md +++ b/text/0000-write-pointers.md @@ -1,4 +1,4 @@ -- Feature Name: parital_initialization_and_write_references +- Feature Name: parital_initialization - Start Date: 2018-08-29 @@ -10,19 +10,18 @@ [summary]: #summary -This RFC aims to allow direct initialization for optimization, and partial struct and enum initialization, for ergonomics. It will do so through the usage of a two new reference type, `&out T` and `&uninit T` (the name is not important to me). +This RFC aims to allow direct ergonomic initialization for optimization, and partial struct and enum initialization. It will do so through the usage of a one new reference type, `&uninit T` (the name is not important to me). # Motivation [motivation]: #motivation -The builder pattern was created as a way to try and solve the issue of not having partial initialization, but it has problems with large structs, and that the `*Builder` struct must necessarily be larger than the target struct, null-ptr optimizations not-withstanding. Also, it is very expensive to move large structs, and relying on the optimizer to optimize out moves isn't very good, `&out T` could serve as a way to directly place things into the desired memory location and to maintain write-only memory. `&uninit T` will serve the purpose of partial initialization and direct initialization. +The builder pattern was created as a way to try and solve the issue of not having partial initialization, but it has problems with large structs, and that the `*Builder` struct must necessarily be larger than the target struct, null-ptr optimizations not-withstanding. Also, it is very expensive to move large structs, and relying on the optimizer to optimize out moves isn't very robust, `&uninit T` could serve as a way to directly place things into the desired memory location and partially initialize that memory. # Guide-level explanation [guide-level-explanation]: #guide-level-explanation -`&out T` is a write-only reference to `T`, where `T: Copy`. The bound is necessary as it is not safe to overwrite `Drop` types without invoking the destructor. Does not run destructors on write. See [unresolved questions](#unresolved-questions) for a more in depth explanation. \ `&uninit T` is a write-once reference to `T`, after the first write, it is allowed to read the values (behaves exactly like `&mut T`). Does not run destructors on the first write. \ For all examples, I will use these two structs @@ -168,7 +167,7 @@ fn invalid_init_bar_v2(bar: &uninit Bar) { } ``` -If a closure captures a `&uninit T`, then it becomes a `FnOnce`, because of the write semantics, the destructors will not be run the first time. +If a closure captures a `&uninit T`, then it becomes a `FnOnce`, because of the write semantics change after the first write. ```Rust let x: Foo; @@ -179,39 +178,6 @@ let init = || x.a = 12; // init: FnOnce() -> () **Note on Panicky Functions:** If a function panics, then all fields initialized in that function will be dropped. No cross-function analysis will be done. -## `&out T` - -Using `&out T`, we can directly initialize a value and guarantee to write only behavior. \ -That would add give a memory location to write to directly instead of relying on move-elimination optimizations. - -```Rust -#[derive(Clone, Copy)] -struct Rgb(pub u8, pub u8, pub u8); -/// This abstraction that exposes a Frame Buffer allocated by the OS, and is unsafe to read from -struct FrameBuffer( ... ); - -impl FrameBuffer { - /// initializes the FrameBuffer in place - fn new(&uninit self) { ... } - - /// gets a write only refernce to pixel at position (row, col) - fn write_to_pixel(&mut self, row: usize, col: usize) -> &out Rgb { - ... - } -} -``` -This could be used like this -```Rust -let buffer; -FrameBuffer::new(&uninit buffer); - -*buffer.write_to_pixel(0, 0) = Rgb(50, 50, 255); -*buffer.write_to_pixel(10, 20) = Rgb(0, 250, 25); -/// ... -``` - -**Note:** `Rgb` is `Copy`, if it wasn't we could not gaurentee that we can safely overwrite it - ## Constructors and Direct Initialization Using `&uninit` we can create constructors for Rust! @@ -239,22 +205,11 @@ impl Vec { } ``` -and maintain write-only buffers -```Rust -struct WriteOnly([u8; 1024]); - -impl WriteOnly { - pub fn write(&out self, byte: u8, location: usize) { - self.0[location] = byte; // currently not possible to index like this, but we could imagine a IndexOut, that will handle this case - } -} -``` - # Reference-level explanation [reference-level-explanation]: #reference-level-explanation -**NOTE** This RFC does NOT aim to create new raw pointer types, so no `*out T` or `*uninit T`. There is no point in creating these. +**NOTE** This RFC does NOT aim to create new raw pointer types, so no `*uninit T`. There is no point in creating these. ## Rules of `&uninit T` @@ -289,15 +244,6 @@ init(a: &uninit Foo) { ... } init(&uninit x); // this function will overwrite, but not drop to the old value of x, so this is a compile-time error ``` -## Rules of `&out T` - -`&out T` should follow some rules in so that is is easy to reason about `&out T` locally and maintain soundness -- `&out T` follows the same rules as `&mut T` for the borrow checker -- Writing does not drop old value. - - Dropping requires at least one read, which is not possible with a write-only reference -- You can take a `&out T` on any `T: Copy` - - because destructors are never run on write, `T: Copy` is necessary to guarantee no custom destructors. - ## Coercion Rules - `&T` // no change @@ -305,10 +251,8 @@ init(&uninit x); // this function will overwrite, but not drop to the old value - `&mut T` - `*mut T` - `&T` - - `&out T` if, `T: Copy` - - `&out T` - - `*mut T` // for similar reasons to why `&T` only coerces to `*const T` - - `&uninit T` - `*mut T`, `&out T` if `T: Copy` and `&T` or `&mut T` once initialized depending on if the variable is mutable or not. + - `&uninit T` - `*mut T`, and `&T` or `&mut T` once initialized depending on if the variable binding is mutable or not. + ```Rust struct Foo(i32, i32); let foo: Foo; @@ -326,11 +270,11 @@ init(&uninit x); // this function will overwrite, but not drop to the old value ## `self` -We will add `&uninit self` and `&out self` as sugar for `self: &uninit Self` and `self: &out Self` respectively. This is for consistency with `&self`, and `&mut self` +We will add `&uninit self` as sugar for `self: &uninit Self`. This is for consistency with `&self`, and `&mut self` ## Panicky functions in detail -Because we can pass `&uninit T` and `&out T` to functions, we must consider what happens if a function panics. For example: +Because we can pass `&uninit T` to functions, we must consider what happens if a function panics. For example: ```Rust fn init_foo_can_panic(foo: &uninit Foo) { foo.b = "Hello World".to_string(); @@ -345,18 +289,9 @@ fn init_foo_can_panic(foo: &uninit Foo) { foo.c = Bar { d = 10, e = 12.0 }; } -fn out_bar_panics(foo: &out Bar) { - // When we panic here we drop here we don't ever drop any value behind a &out because &out can never have a destructor, it doesn't matter - panic!("Oh no, something went wrong!"); -} - let x: Foo; init_foo_can_panic(&uninit x); - -let x: Bar; - -out_bar_panics(&out x); // when we take a &out, we are asserting that the old value doesn't need to drop, and doesn't matter. This is fine because Bar is Copy and does not have a destructor. ``` # Drawbacks @@ -370,10 +305,6 @@ out_bar_panics(&out x); // when we take a &out, we are asserting that the old va [rationale-and-alternatives]: #rationale-and-alternatives -## `T: !Drop` for `&out T` - -If negative trait bounds become stable, the bounds for `&out T` will change to `T: !Drop`. But this does not seem like the correct bound, see [here](#unresolved-questions) for why. - ## Allow Drop types to be partially initialized Then they would only be dropped if all of their fields are initialized @@ -404,24 +335,18 @@ x = Bar { d: xd, e: xe }; But this would not be able to replace placement new as it can't handle `&uninit T` through function boundaries. Also this would not solve the problem of direct-initialization. +## `MaybeInit` + +`MaybeInit` is almost like `&uninit`, but it requires the use of unsafe to get a value out of it, and it does not work on a value in place, i.e. the value must be moved out of the `MaybeInit` after initialization. + # Prior art [prior-art]: #prior-art -Out pointers in C++, (not exactly the same, but similar idea) - -`out T` in C# +~~ # Unresolved questions [unresolved-questions]: #unresolved-questions - - What is the correct bounds for `T` in `&out T`? conservatively, `T: Copy` works and is sound. But as [@TechnoMancer](https://internals.rust-lang.org/u/TechnoMancer) pointed out, `T: !Drop` is not the correct bound. For example, `Wrapper(Vec)`, clearly cannot be overwritten safely, because `Vec`, must drop do deallocate the `Vec`, but `Wrapper` itself does not implement drop. Therefore either a new trait is needed (but unwanted), or we must keep the `Copy` bound. - - On this note should we even have a bound on `T` for `&out T`, it does not break memory safety to remove the `Copy` bound, but may cause logic errors. So this error could be demoted to an error-by-default-lint instead. - - Should we introduce a `*out T` raw pointer, that does not drop its value upon write, for all `T: Copy`. - - `&out T` coerces to `*out T` - - no reading from `*out T` - - Should we introduce a trait `OverwriteSafe`, which is auto-implemented (like `Send` and `Sync`) where all types `T` are `OverwriteSafe` unless, and use this as the bound for `&out T` - - `T: Drop` - - one of `T`'s fields is not `OverwriteSafe` - We can use drop flags to check when a variable has been initialized, and use that information to allow conditional initialization. But should we do this? --- @@ -462,6 +387,10 @@ Removed the Casting Rules Section, because it was wrong, instead to convert raw Added an unresolved question, about use of drop flags to allow for conditional initialization. +edit 8: + +Removed all bits about `&out T` because `&out T` can be implemented as a library item see [my comment here](https://github.com/rust-lang/rfcs/pull/2534#issuecomment-453720019) for details about that. + --- I would like to thank all the people who helped refine this proposal to its current state: [@rkruppe](https://internals.rust-lang.org/u/rkruppe), [@earthengine](https://internals.rust-lang.org/u/earthengine), [@gbutler](https://internals.rust-lang.org/u/gbutler), and [@TechnoMancer](https://internals.rust-lang.org/u/TechnoMancer) in the Pre-RFC, and