From 2d418f6febfebeb88942455e91b33d4c8775ed6c Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 21 Dec 2015 15:43:55 -0800 Subject: [PATCH 1/7] Amends and extends 0809 (placement new/box). Supersedes #1401. --- text/0000-placement-traits.md | 312 ++++++++++++++++++++++++++++++++++ 1 file changed, 312 insertions(+) create mode 100644 text/0000-placement-traits.md diff --git a/text/0000-placement-traits.md b/text/0000-placement-traits.md new file mode 100644 index 00000000000..5c638323fbc --- /dev/null +++ b/text/0000-placement-traits.md @@ -0,0 +1,312 @@ +- Feature Name: placement_traits +- Start Date: 2015-12-21 +- RFC PR: (leave this empty) +- Rust Issue: (leave this empty) + +# Summary +[summary]: #summary + +This RFC amends RFC #0809 to reduce the number of traits involved, take +allocators into account, and pin down the story on DST placement. + +## Traits + +There are now three traits: + +1. `Placer` -- Placement in syntax: `let owner = PLACE <- value`. +2. `Boxer` -- Box syntax: `let boxed: Box<_> = box value`. +3. `Place` -- An "out" pointer. + +## Allocators + +Boxer's now make Places from Allocators. This means that any type implementing +`Boxer` can be allocated with any Allocator using placement in syntax (see the +detailed design for more). + +## DSTs + +`Boxer::make_place` and `Placer::make_place` are bounded by `Data: Sized` to +future proof against DST placement. + +Furthermore, this RFC explicitly defines the guarantees of when/where +`Placer::make_place` will be called. + +# Detailed design +[design]: #detailed-design + +The new trait hierarchy is as follows: + +```rust +/// Interface to implementations of `PLACE <- EXPR`. +/// +/// `PLACE <- EXPR` effectively desugars into: +/// +/// ```rust,ignore +/// let p = PLACE; +/// let mut place = Placer::make_place(p); +/// let raw_place = Place::pointer(&mut place); +/// let value = EXPR; +/// unsafe { +/// std::ptr::write(raw_place, value); +/// Place::finalize(place) +/// } +/// ``` +/// +/// The type of `PLACE <- EXPR` is derived from the type of `PLACE` and the +/// context. If the type of `PLACE` is `P`, then the final type of the +/// expression is some owner such that `P` implements `Placer`. +/// +/// Values for types implementing this trait usually are transient +/// intermediate values (e.g. the return value of `Vec::back`) +/// or `Copy`, since the `make_place` method takes `self` by value. +pub trait Placer { + /// `Place` is the intermedate agent guarding the + /// uninitialized state for `Data`. + type Place: Place; + + /// Creates a fresh place from `self`. + fn make_place(self) -> Self::Place + where Data: Sized; +} + +/// Core trait for the `box EXPR` form. +/// +/// `box EXPR` effectively desugars into: +/// +/// ```rust,ignore +/// let mut place = Boxer::make_place(Default::default()); +/// let raw_place = Place::pointer(&mut place); +/// let value = EXPR; +/// unsafe { +/// ::std::ptr::write(raw_place, value); +/// Place::finalize(place) +/// } +/// ``` +/// +/// The type of `box EXPR` is supplied from its surrounding +/// context; in the above expansion, the result type `T` is used +/// to determine which implementation of `Boxer` to use, and that +/// `` in turn dictates determines both which `Allocator` +/// to use and which implementation of `Place` to use. +pub trait Boxer: Sized + where A: Allocator +{ + /// The place that will negotiate the storage of the data. + type Place: Place; + + /// Creates a globally fresh place from a given allocator. + fn make_place(allocator: A) -> Self::Place + where Data: Sized; +} + +/// Both `PLACE <- EXPR` and `box EXPR` desugar into expressions +/// that allocate an intermediate "place" that holds uninitialized +/// state. The desugaring evaluates EXPR, and writes the result at +/// the address returned by the `pointer` method of this trait. +/// +/// A `Place` can be thought of as a special representation for a +/// hypothetical `&uninit` reference (which Rust cannot currently +/// express directly). That is, it represents a pointer to +/// uninitialized storage. +/// +/// The client is responsible for two steps: First, initializing the +/// payload (it can access its address via `pointer`). Second, +/// converting the agent to an instance of the owning pointer, via the +/// appropriate `finalize` method. +/// +/// If evaluating EXPR fails, then the destructor for the +/// implementation of Place is run to clean up any intermediate state +/// (e.g. deallocate box storage, pop a stack, etc). +pub unsafe trait Place { + /// `Owner` is the type of the end value of both `PLACE <- EXPR` and + /// `box EXPR`. + /// + /// Note that when `PLACE <- EXPR` is solely used for side-effecting an + /// existing data-structure, e.g. `Vec::back`, then `Owner` need not carry + /// any information at all (e.g. it can be the unit type `()` in that + /// case). + type Owner; + + /// Returns the address where the input value will be written. + /// Note that the data at this address is generally uninitialized, + /// and thus one should use `ptr::write` for initializing it. + fn pointer(&mut self) -> *mut Data; + + /// Converts self into the final value, shifting deallocation/cleanup + /// responsibilities (if any remain), over to the returned instance of + /// `Owner` and forgetting self. + unsafe fn finalize(self) -> Self::Owner; +} + +``` + +First, `box` desugaring constructs the allocator with `Default::default()`. This +means that `let boxed: Box<_, A> = box value;` works for all allocators `A: +Default`. It's reasonable to construct new allocators on the fly like this +because allocators are intended to be passed to collection constructors by +value. + +Additionally, we define the following blanket impl that turns every `Allocator` +into a `Placer` for all `Boxer`s. + +```rust +impl Placer for T + where T: Allocator, + B: Boxer +{ + type Place = B::Place; + + fn make_place(self) -> Self::Place + where Self: Sized + { + B::make_place(self) + } +} +``` + +This means that `let boxed_thing: Type<_> = HEAP <- thing` works out of the box +as long as `HEAP` is an `Allocator` and `Type` implements `Boxer`. + +Finally, to support DST placement, this RFC explicitly loosens the placement +protocol guarantees. Specifically, the place in placement in/new is not +guaranteed to be allocated before the evaluation of the expression on the right +hand side. DST placement needs this to be able to compute the size of the DST +before allocating the place. This means that, in the following cases, whether or +not the `Box` is ever allocated is explicitly undefined: + +```rust +let _: Box<_> = box panic!(); +let _: Box<_> = HEAP <- panic!(); +``` + +For completeness, I've included the following DST placement traits to +demonstrate that the current placement traits are compatible with DST placement. +Note: A concrete design for returning DSTs is well outside the scope of this +RFC. + +```rust +trait DstPlacer: Placer { + fn make_place_dynamic(self, layout: Layout) -> Self::Place; +} + +trait DstBoxer: Placer where A: Allocator { + fn make_place_dynamic(allocator: A, layout: Layout) -> Self::Place; +} + +impl DstPlacer for T + where T: Allocator, + B: DstBoxer +{ + type Place = B::Place; + + fn make_place_dynamic(self, layout: Layout) -> Self::Place { + B::make_place_dynamic(self, layout) + } +} + +// Assuming specialization. +default impl Placer for T where T: DstPlacer { + fn make_place(self, layout: Layout) -> Self::Place + where Self: Sized, + { + self.make_place_dynamic(Layout::new::()) + } +} + +default impl Boxer for T + where T: DstBoxer + A: Allocator +{ + fn make_place(allocator: A) -> Self::Place + where Self: Sized, + { + self.make_place_dynamic(Layout::new::()) + } +} +``` + +## Choices + +### Placer::make_place + +`Placer::make_place` takes self by value. Taking self by reference as discussed +in #1286 would either require an option dance, HKT to properly handle lifetimes +(`type Place<'a> = ...`), or shenanigans. Furthermore, taking self by value +ensures that non-copy placers are used only once. This allows panic-free +allocation using `try!(place) <- thing`. + +### Parameterize Placer with Owner + +`Owner` is now a type parameter in `Placer`. This allows placement in syntax to +be used with allocators. That is: + +```rust +let boxed_thing: Box<_> = HEAP <- thing1; +let rced_thing: Rc<_> = HEAP <- thing2; +``` + +If `PLACER <- thing` can have precisely one type as in the original RFC, it +wouldn't be possible to produce both `Rc`s and `Box`s (see alternatives for an +alternative solution). + +# Drawbacks +[drawbacks]: #drawbacks + +## Placer::make_place self by value + +Taking self by-value means that `vec <- value` won't work. However, `vec.back() +<- value` still works just fine so I'm not convinced this is a problem. + +The other drawback is that `arena <- value` won't work either. One solution is +to autoref the placer if necessary but, + +1. I'm not entirely convinced this use case is common enough to be worth it. +2. This is a backwards compatible change that can be made at any time. + +## Place taking Owner as a type parameter + +This could interfere with type inference but shouldn't be an issue. In most +cases, `Placer` will only be defined for one `Owner` per type. + +# Alternatives +[alternatives]: #alternatives + +Instead of making the `Placer` trait take `Owner` as a type argument, we could +add the following default method to the `Allocator` trait: + +```rust +trait Allocator { + /* ... */ + fn emplace(self) -> BoxPlacer { + BoxPlacer { + allocator: self, + _marker: PhantomData, + } + } +} + +pub struct BoxPlacer { + allocator: A, + _marker: PhantomData(fn() -> B) +} + +impl Placer for BoxPlacer + where B: Boxer, + A: Allocator, +{ + fn make_place(self) -> B::Place + where Self: Sized, + { + >::make_place(self) + } +} +``` + +We could then use `let boxed = HEAP.emplace::>() <- value;` to select +the output type. However, IMO, this is much less ergonomic. + +# Unresolved questions +[unresolved]: #unresolved-questions + +Does `Place::pointer` need to return an `&mut T` (or some other special pointer) +to ensure RVO? Should it return `std::ptr::Unique`? From 8fad6e9917cf2cd3218566184697c84cbf630d10 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Sat, 26 Dec 2015 11:51:05 -0800 Subject: [PATCH 2/7] Make Place's Data an associated type. --- text/0000-placement-traits.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/text/0000-placement-traits.md b/text/0000-placement-traits.md index 5c638323fbc..d00bcfd7920 100644 --- a/text/0000-placement-traits.md +++ b/text/0000-placement-traits.md @@ -62,7 +62,7 @@ The new trait hierarchy is as follows: pub trait Placer { /// `Place` is the intermedate agent guarding the /// uninitialized state for `Data`. - type Place: Place; + type Place: Place; /// Creates a fresh place from `self`. fn make_place(self) -> Self::Place @@ -92,7 +92,7 @@ pub trait Boxer: Sized where A: Allocator { /// The place that will negotiate the storage of the data. - type Place: Place; + type Place: Place; /// Creates a globally fresh place from a given allocator. fn make_place(allocator: A) -> Self::Place @@ -117,7 +117,7 @@ pub trait Boxer: Sized /// If evaluating EXPR fails, then the destructor for the /// implementation of Place is run to clean up any intermediate state /// (e.g. deallocate box storage, pop a stack, etc). -pub unsafe trait Place { +pub unsafe trait Place { /// `Owner` is the type of the end value of both `PLACE <- EXPR` and /// `box EXPR`. /// @@ -127,10 +127,13 @@ pub unsafe trait Place { /// case). type Owner; + /// `Data` is the type of the value to be emplaced. + type Data: ?Sized; + /// Returns the address where the input value will be written. /// Note that the data at this address is generally uninitialized, /// and thus one should use `ptr::write` for initializing it. - fn pointer(&mut self) -> *mut Data; + fn pointer(&mut self) -> *mut Self::Data; /// Converts self into the final value, shifting deallocation/cleanup /// responsibilities (if any remain), over to the returned instance of From b05f4207e0b9422dc69a94b6af337428fa083ced Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Sun, 27 Dec 2015 11:39:16 -0800 Subject: [PATCH 3/7] Fix placement traits grammar nits. --- text/0000-placement-traits.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/text/0000-placement-traits.md b/text/0000-placement-traits.md index d00bcfd7920..61544fd6061 100644 --- a/text/0000-placement-traits.md +++ b/text/0000-placement-traits.md @@ -19,9 +19,9 @@ There are now three traits: ## Allocators -Boxer's now make Places from Allocators. This means that any type implementing -`Boxer` can be allocated with any Allocator using placement in syntax (see the -detailed design for more). +`Boxer`s now make `Place`s from `Allocator`s. This means that any type +implementing `Boxer` can be allocated with any `Allocator` using placement in +syntax (see the detailed design for more). ## DSTs From 80f76ed3343d86c56459a4834f196bba5f5d7a59 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Sun, 27 Dec 2015 13:01:13 -0800 Subject: [PATCH 4/7] stack frame not stack --- text/0000-placement-traits.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-placement-traits.md b/text/0000-placement-traits.md index 61544fd6061..af675ac8eaf 100644 --- a/text/0000-placement-traits.md +++ b/text/0000-placement-traits.md @@ -116,7 +116,7 @@ pub trait Boxer: Sized /// /// If evaluating EXPR fails, then the destructor for the /// implementation of Place is run to clean up any intermediate state -/// (e.g. deallocate box storage, pop a stack, etc). +/// (e.g. deallocate box storage, pop a stack frame, etc). pub unsafe trait Place { /// `Owner` is the type of the end value of both `PLACE <- EXPR` and /// `box EXPR`. From 42cfbd056514dd2f54f07fc42a05fe0873a9f95c Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Sat, 2 Jan 2016 13:14:20 -0800 Subject: [PATCH 5/7] Address feedback. 1. Don't have Boxers take Allocators. 2. Explain why we need the Placer trait. --- text/0000-placement-traits.md | 105 ++++++++++++++++++++++++---------- 1 file changed, 76 insertions(+), 29 deletions(-) diff --git a/text/0000-placement-traits.md b/text/0000-placement-traits.md index af675ac8eaf..7448541ee12 100644 --- a/text/0000-placement-traits.md +++ b/text/0000-placement-traits.md @@ -11,17 +11,19 @@ allocators into account, and pin down the story on DST placement. ## Traits -There are now three traits: +There are now four traits: 1. `Placer` -- Placement in syntax: `let owner = PLACE <- value`. 2. `Boxer` -- Box syntax: `let boxed: Box<_> = box value`. 3. `Place` -- An "out" pointer. +4. `Allocate` -- A trait that allows boxing and placing into `Allocator`s. ## Allocators -`Boxer`s now make `Place`s from `Allocator`s. This means that any type -implementing `Boxer` can be allocated with any `Allocator` using placement in -syntax (see the detailed design for more). +This RFC adds a new `Allocate` trait that can be implemented by any type that +takes an allocator. Implementing `Allocate` automatically implements `Boxer` +for boxing into allocators that implement `Default` and allows the type to be +allocated with a custom allocator using the `ALLOCATOR <- value` syntax. ## DSTs @@ -74,7 +76,7 @@ pub trait Placer { /// `box EXPR` effectively desugars into: /// /// ```rust,ignore -/// let mut place = Boxer::make_place(Default::default()); +/// let mut place = Boxer::make_place(); /// let raw_place = Place::pointer(&mut place); /// let value = EXPR; /// unsafe { @@ -86,16 +88,14 @@ pub trait Placer { /// The type of `box EXPR` is supplied from its surrounding /// context; in the above expansion, the result type `T` is used /// to determine which implementation of `Boxer` to use, and that -/// `` in turn dictates determines both which `Allocator` -/// to use and which implementation of `Place` to use. -pub trait Boxer: Sized - where A: Allocator -{ +/// `` in turn dictates which implementation of `Place` to use +/// (specifically, `>::Place`). +pub trait Boxer: Sized { /// The place that will negotiate the storage of the data. type Place: Place; - /// Creates a globally fresh place from a given allocator. - fn make_place(allocator: A) -> Self::Place + /// Creates a globally fresh place. + fn make_place() -> Self::Place where Data: Sized; } @@ -141,34 +141,60 @@ pub unsafe trait Place { unsafe fn finalize(self) -> Self::Owner; } -``` +/// All types implemeinting this trait can be constructed using using the `box` +/// keyword (iff the associated `Allocator` implements `Default`) or by placing +/// (`<-`) a value into an instance of the associated `Allocator`. +/// +/// ```norun +/// let boxed1: Box = box 1; // where SomeAllocator: Default +/// let boxed2: Box = SomeAllocator::new() <- 1; +/// ``` +pub trait Allocate: Sized { + /// The place that will negotiate the storage of the data. + type Place: Place; + /// The allocator for this type. + type Allocator: Allocator; -First, `box` desugaring constructs the allocator with `Default::default()`. This -means that `let boxed: Box<_, A> = box value;` works for all allocators `A: -Default`. It's reasonable to construct new allocators on the fly like this -because allocators are intended to be passed to collection constructors by -value. + /// Create a `Place` for `Self` with the given `Allocator`. + fn allocate(with: Self::Allocator) -> Self::Place + where Data: Sized; +} + +``` -Additionally, we define the following blanket impl that turns every `Allocator` -into a `Placer` for all `Boxer`s. +Additionally, we define the following blanket impls to allow anything +implementing `Allocate` to be allocated in any `Allocator`. When specialization +lands, these can be marked default. ```rust -impl Placer for T +// Allows `let owner: T = ALLOCATOR <- thing;` +impl Placer for T where T: Allocator, - B: Boxer + A: Allocate { - type Place = B::Place; + type Place = A::Place; fn make_place(self) -> Self::Place where Self: Sized { - B::make_place(self) + A::allocate(self) } } -``` -This means that `let boxed_thing: Type<_> = HEAP <- thing` works out of the box -as long as `HEAP` is an `Allocator` and `Type` implements `Boxer`. +// Allows `let owner: T = box thing;` +impl Boxer for A + where A: Allocate, + A::Allocator: Default +{ + type Place = A::Place; + + fn make_place() -> Self::Place + where Self: Sized + { + A::allocate(Default::default()) + } +} +``` Finally, to support DST placement, this RFC explicitly loosens the placement protocol guarantees. Specifically, the place in placement in/new is not @@ -274,6 +300,22 @@ cases, `Placer` will only be defined for one `Owner` per type. # Alternatives [alternatives]: #alternatives +## Get rid of Placer + +Instead of having a `Placer`, we could allow placing directly into `Place`s. +Unfortunately, to allow expressions that may or may not be placed into (e.g. +`map.entry(k) <- v`), the `Place` would have to allocate in `Place::pointer` +instead of on construction to avoid allocating unnecessarily. This, in turn, +means that the `Place` would have to manually keep track of whether or not it +has allocated. Using a `Placer` allows us to store this in the type system +itself which: + +1. Is less error prone. +2. Is probably more optimizable. + + +## Placer with an associated Owner + Instead of making the `Placer` trait take `Owner` as a type argument, we could add the following default method to the `Allocator` trait: @@ -308,8 +350,13 @@ impl Placer for BoxPlacer We could then use `let boxed = HEAP.emplace::>() <- value;` to select the output type. However, IMO, this is much less ergonomic. +## The Allocate trait + +Instead of having an `Allocate` trait, we could have all `Boxer`s take +`Allocator`s. However, as @nagisa has pointed out, not all `Boxer`s will need +an `Allocator` (some may, e.g., use object pools). + # Unresolved questions [unresolved]: #unresolved-questions -Does `Place::pointer` need to return an `&mut T` (or some other special pointer) -to ensure RVO? Should it return `std::ptr::Unique`? +None. From 1a65885c5d254858e8aa9948c8cca9fb0e850793 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Thu, 28 Jan 2016 13:35:37 -0500 Subject: [PATCH 6/7] Fix spelling --- text/0000-placement-traits.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-placement-traits.md b/text/0000-placement-traits.md index 7448541ee12..988b4c6caa3 100644 --- a/text/0000-placement-traits.md +++ b/text/0000-placement-traits.md @@ -141,7 +141,7 @@ pub unsafe trait Place { unsafe fn finalize(self) -> Self::Owner; } -/// All types implemeinting this trait can be constructed using using the `box` +/// All types implementing this trait can be constructed using using the `box` /// keyword (iff the associated `Allocator` implements `Default`) or by placing /// (`<-`) a value into an instance of the associated `Allocator`. /// From 55aeff8941b5c1a72e27431273dae933a40226e2 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Thu, 28 Jan 2016 13:46:39 -0500 Subject: [PATCH 7/7] Make it clear that expressions are always evaluated LTR --- text/0000-placement-traits.md | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/text/0000-placement-traits.md b/text/0000-placement-traits.md index 988b4c6caa3..3ea42d3c1f8 100644 --- a/text/0000-placement-traits.md +++ b/text/0000-placement-traits.md @@ -197,17 +197,31 @@ impl Boxer for A ``` Finally, to support DST placement, this RFC explicitly loosens the placement -protocol guarantees. Specifically, the place in placement in/new is not -guaranteed to be allocated before the evaluation of the expression on the right -hand side. DST placement needs this to be able to compute the size of the DST -before allocating the place. This means that, in the following cases, whether or -not the `Box` is ever allocated is explicitly undefined: +protocol guarantees. Specifically, while `PLACE <- THING` is still guaranteed to +be evaluated left-to-right, the place is not guaranteed to be allocated before +of the expression on the right hand side has been fully evaluated. That is, +`Placer::make_place` may be called any time during the evaluation of `THING`. +DST placement needs this to be able to compute the size of the DST before +allocating the place. + +For example, in the following cases, whether or not the `Box` is ever allocated +is explicitly undefined: ```rust let _: Box<_> = box panic!(); let _: Box<_> = HEAP <- panic!(); ``` +However, in the following case, "here" will always be printed because the +expression is still guaranteed to be evaluated left-to-right: + +```rust +let _: Box<_> = { + println!("here"); + HEAP +} <- panic!(); +``` + For completeness, I've included the following DST placement traits to demonstrate that the current placement traits are compatible with DST placement. Note: A concrete design for returning DSTs is well outside the scope of this