-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
RFC for placement box with Placer trait for overloading. #470
Conversation
I'm not sure placement box is in general a good design. The big issue is that the only thing that it solves is elimination of a temporary, but only in a specific case and in a way that does not easily compose. For example, let's say that you want to create a mechanism to box T into a Box<Option> by using Some or a mechanism to box two values in a pair. The natural composable way to do so is this:
The problem is that this results in creating temporaries again, even if we have placement box... and the only way to avoid this is an extremely ugly hack with unsafe code that implements the placement box traits to do this simple construction operation. Also it seems it's completely impossible to box more than one object (for instance, by boxing as a pair) without temporaries even with hacks in this RFC. This RFC also provides an example of "emplace back" that takes 54 lines of code (!) for something that should take 1 line. IMHO any design, such as the one in this RFC, that doesn't allow such composability easily should probably be avoided. I think the simplest solution is to just remove the box keyword, use Box::new and rely on inlining and the optimizer to remove the temporaries, with an annotation on heap allocation functions that tells LLVM that constant propagation, move elimination and similar can be done across heap allocation. |
@bill-myers you make good points, but I can do without hyperbole like this:
A lot of the code in that example can be removed and is only there for expository purposes (or for testing in my prototype). I do (and did) acknowledge that the code there is overcomplicated by the protocol's use of two separate traits; see discussion in alternatives section Having said that, the composability problem is an interesting one. I'm not sure a solution exists that is (1.) safe, (2.) composable in the way you outline, and (3.) guaranteed to avoid a temporary. This is not just about the stdlib's heap allocation functions; it is also about user-defined memory abstractions, like arenas. But maybe you are suggesting that even the latter should receive the annotations to convince LLVM that it is safe to do optimizations across their procedure call boundary? |
I just remembered: @nikomatsakis told me a while back that a number of the types I wrote as type parameters should instead be associated types. I will try to update the RFC accordingly tomorrow or Thursday. |
Relying on the implementation details of the optimiser seems unfortunate. Also, we would need to make it happen even without optimisations (otherwise, say, |
A lot of the apparent complexity in this RFC appears to come from a lack of a safe way to talk about "out" pointers, or pointers that are not initialized and are meant to be initialized before use, "move" pointers that take ownership of the thing they are referencing without moving or boxing it, and the lack of HKT. This whole thing could easily be written as just: pub trait Placer {
type Place<*>; // Theoretical HKT syntax for a * -> * type.
fn place<T>(&mut self) -> &out T;
fn finalize<T>(&mut self, &move T) -> Place<T>;
} (or something similar) if we had Here's a sample implementation of // Box
pub struct BoxPlacer;
impl Placer for BoxPlacer {
type Place = Box;
fn place<T>(&mut self) -> &out T {
let size = mem::size_of::<T>();
let align = mem::align_of::<T>();
if size == 0 {
(heap::EMPTY as *mut u8).into_out()
} else {
let p = unsafe {
// into_out does a NULL check
// this implementation could be made *safe* if there was a heap::allocate_out
heap::allocate(size, align).into_out()
};
}
}
fn finalize<T>(&mut self, val: &move T) -> Box<T> {
unsafe { mem::transmute::<&move T, Box<T>>(val) }
}
}
// And emplace back
pub struct Emplacer(&mut Vec<()>);
impl<T> Placer for Emplacer<T> {
type Place = // some standard `* -> ()` HKT goes here
fn place<T>(&mut self) -> &out T {
// Grows the Vec if needed and gets an &out reference to a spot past the end, implementation detail.
self.0.place_back()
}
fn finalize<T>(&mut self) {}
} For the record, I have clearly not given this nearly as much thought as @pnkfelix has in this RFC, I just think it's worth questioning whether future language features could vastly simplify the situation here, and, if they could, whether it's worth adding this feature now or just reserving the necessary syntax. |
@reem ah I think your comment is a much much more coherent description of what I was thinking of when discussing |
I think the major hole in the implementation I proposed above is that it doesn't handle exceptions while evaluating Something like: pub trait OutHandle<T> {
fn out(self) -> &out T;
} |
@bill-myers, can you please expand on why tuple placement is not possible without allocating temporaries? |
I completely agree with @reem regarding (As an aside, the current return value optimization also kinda creeps me out (despite my background functional programming), and I wonder if something a bit more explicit but still ergonomic could be done instead.)
Once-closure that takes a
The double try is a bit unfortunate, but what if : let b: PBox<T> = try!(box (place) try!(run_code())); becomes: let b: PBox<T> = try!(place.make_box::<_>(|once: ptr: &out T| -> R<T> {
ptr = try!(run_code());
// Just need some way to add the Ok(())
},)); Granted, I punt on the |
So conversation here has slowed down, but I want to add my current thoughts. Let's leave aside the particulars of the protocol for the moment. The high-level question is this: for 1.0, we want to have an in-place allocation / construction syntax, or if we want to start without one and bring one in later. The argument in favor of adding the syntax now is probably two-fold. First, there is a consistency argument. If we were to remove this syntax, it would mean that (for the time being) the The argument against adding the syntax is that it is clearly not necessary for 1.0 and we may be able to concoct a better protocol in the future. It would also allow us to introduce the We had this discussion in a recent meeting (last Tuesday) and the general feeling was to lean towards removing box from the 1.0 release. That said, it's important to recognize that this too requires some effort (first, measurement to see its impact, but also just editing the existing code). |
@nikomatsakis Why could we not maintain the status quo and simply special-case I am for waiting for post-1.0 to implement extensible |
closing in favor of the more conservative #809 |
…sakis Remove all unstable placement features Closes #22181, #27779. Effectively makes the assortment of placement RFCs (rust-lang/rfcs#470, rust-lang/rfcs#809, rust-lang/rfcs#1228) 'unaccepted'. It leaves `box_syntax` and keeps the `<-` token as recognised by libsyntax. ------------------------ I don't know the correct process for unaccepting an unstable feature that was accepted as an RFC so...here's a PR. Let me preface this by saying I'm not particularly happy about doing this (I know it'll be unpopular), but I think it's the most honest expression of how things stand today. I've been motivated by a [post on reddit](https://www.reddit.com/r/rust/comments/7wrqk2/when_will_box_and_placementin_syntax_be_stable/) which asks when these features will be stable - the features have received little RFC-style design work since the end of 2015 (~2 years ago) and leaving them in limbo confuses people who want to know where they're up to. Without additional design work that needs to happen (see the collection of unresolved questions later in this post) they can't really get stabilised, and I think that design work would be most suited to an RFC rather than (currently mostly unused) experimental features in Rust nightly. I have my own motivations - it's very simple to 'defeat' placement in debug mode today and I don't want a placement in Rust that a) has no guarantees to work and b) has no plan for in-place serde deserialisation. There's a quote in [1]: "Ordinarily these uncertainties might lead to the RFC being postponed. [The RFC seems like a promising direction hence we will accept since it] will thus give us immediate experience with the design and help in determining the best final solution.". I propose that there have been enough additional uncertainties raised since then that the original direction is less promising and we should be think about the problem anew. (a historical note: the first mention of placement (under that name - uninit pointers were earlier) in an RFC AFAIK is [0] in late 2014 (pre-1.0). RFCs since then have built on this base - [1] is a comment in Feb 2015 accepting a more conservative design of the Place* traits - this is back when serde still required aster and seemed to break every other nightly! A lot has changed since then, perhaps placement should too) ------------------------ Concrete unresolved questions include: - making placement work in debug mode [7] - making placement work for serde/with fallible creation [5], [irlo2], [8] - trait design: - opting into not consuming the placer in `Placer::make_place` - [2] - trait proliferation - [4] (+ others in that thread) - fallible allocation - [3], [4] (+ others in that thread) - support for DSTs/unsized structs (if at all) - [1], [6] More speculative unresolved questions include: - better trait design with in the context of future language features [irlo1] (Q11), [irlo3] - interaction between custom allocators and placement [irlo3] [0] rust-lang/rfcs#470 [1] rust-lang/rfcs#809 (comment) [2] rust-lang/rfcs#1286 [3] rust-lang/rfcs#1315 [4] #27779 (comment) [5] #27779 (comment) [6] #27779 (comment) [7] #27779 (comment) [8] rust-lang/rfcs#1228 (comment) [irlo1] https://internals.rust-lang.org/t/placement-nwbi-faq-new-box-in-left-arrow/2789 [irlo2] https://internals.rust-lang.org/t/placement-nwbi-faq-new-box-in-left-arrow/2789/19 [irlo3] https://internals.rust-lang.org/t/lang-team-minutes-feature-status-report-placement-in-and-box/4646
Summary
Add user-defined placement
in
expression (more succinctly, "an in expression"), an operator analogous to "placement new" in C++. This provides a way for a user to specify (1.) how the backing storage for some datum should be allocated, (2.) that the allocation should be ordered before the evaluation of the datum, and (3.) that the datum should preferably be stored directly into the backing storage (rather than allocating temporary storage on the stack and then copying the datum from the stack into the backing storage).This RFC does not suggest any change to the non-placement box expression special form (
box <value-expr>
); a future RFC is planned that will suggest changes to that form based on preliminary feedback to this RFC, but that is orthogonal to this RFC's goals.rendered