Placement NWBI<- FAQ (New/Box/In/Left Arrow)

From my point of view, a struct implementing the Placer trait IS an uninitialized pointer, but a rich one, that understands cleanup and so forth.

1 Like

Some questions that need to be added to the FAQ (see Rust Issue 27779 and RFC Issue 1315):

  • Q Why are is there both a Placer and a Place trait? Can we not make do with just one?

  • Q How is fallible allocation supposed to be handled?

  • Q Are we really planning to have Placer global constants (e.g. HEAP, RC, from some of the original discussions of the protocols).

Shouldn’t the Place trait (or at least InPlace) be an unsafe trait if the placement protocol is relying on it returning a valid, non-null pointer?

1 Like

Shouldn’t the Place trait (or at least InPlace) be an unsafe trait if the placement protocol is relying on it returning a valid, non-null pointer?

Hmm, the original API designs I think predated unsafe traits … now that you mention this, I can’t think of a good reason not to do it.

After I posted that I realised that all the protocol relies on is the Placer to return a valid Place, so if anything should be an unsafe trait it should probably be that. Sorry about that, I was a little confused about why 3 traits were necessary, but I think I understand now.

It seems like making Placer unsafe would prevent creating a wrapper around a struct implementing Placer in safe code. E.g., say I want to create a custom type that I want to support placement into. It would be nice if I could, without using unsafe, have an internal Vec or TypedArena (depending on the desired semantics) to which I delegate my Placer::make_place implementation.

1 Like

yes I agree.

I think the original suggestion of making Place itself an unsafe trait will cover the wrapper use-case, right?

It would seem so to me.

I was thinking about this, and I realized that while making Place an unsafe trait does allow the wrapper use case with safe code, it does make the abstraction leaky. That is, without being able to also wrap Place, code using the wrapper will be able to see that the Place returned is a VecPlaceBack (or whatever), and treat it as such. Thus, changing the inner container could technically cause breakage.

One way to solve this problem would be to introduce some kind of abstract associated type to go along with the proposed idea for abstract function return types. This would prevent the code using the wrapper from relying on the specific type implementing Place.

FWIW, I think this would be a natural extension of the abstract type feature discussed in/on the new RFC by @Kimundi (that is, allowing an associated type to be defined as an abstract type in an impl). Basically the same idea is described in the "Translating Abstract Types" section of the paper ML Modules and Haskell Type Classes: A Constructive Comparison (longer version). (I suspect this may also be the same thing @eddyb and @aturon were discussing before I had even understood what it's about - or in @eddyb's case, implementing.)

My head is too small for the placer protocol to fit inside, but a different direction that may also be applicable would be, considering that our "associated types" correspond to Glasgow Haskell's "type families", to add the equivalent of data family as well, which in Rust parlance would probably be called "associated structs". In that case there would be a direct 1-to-1 mapping between types implementing the trait and their associated structs (or enums), instead of an associated type which maps onto a previously-declared type. (I'm not sure whether this obviates the "changing the inner container" possibility.)

Having recently re-read this FAQ, I’ve developed a couple of new-ish thoughts :slightly_smiling::

I don’t see how this could work without relying on RVO. What if the VALUE is a function call? IIRC, the idea was that large values are returned by pointer into the caller-allocated space anyways, so all that box expression needs to do is ensure that pointer passed to the callee is the one returned by the placer. Why wouldn’t this work with closures?

Yeah, but if run_code() returns Result<T,E>, how can we avoid creating a temporary value for it? We can’t put it into the memory allocated by the placer, since Result<T,..> is larger than T (in most cases), so it’d have to go on the stack, which kinda defeats the whole purpose of using placement operator…

This point is not about space overhead. The point is that the try! expands to control flow (specifically a return in the error case), and therefore putting it into the closure is incorrect: It won’t return from the function that defines b, it only returns from the closure. And what’s worse, it doesn’t even compile because the closure must return T but in the error case try! would return Result<T>.

I disagree. Elimination of temporaries is the whole point of the placement operator. Otherwise, you might as well write it as Box::new(try!(make_value()))

Box::new was never supposed to exist.

Sadly, it might be a bit late for universal placement, but the goal was to have a single way to allocate/emplace, with maximal efficiency and minimal syntactical overhead.

If successful, Box::new, Vec::push etc. will be deprecated.

1 Like

Vec::push cannot be deprecated because there are two possible semantics here - push to the vector and fail if there is no more capacity, or push and reallocate the entire vector if necessary. Unless the API will dictate to replace the latter semantics, arguably the common case atm, with a capacity check before pushing to the vector. Seems to me that such a choice will make for a bad API experience. The former is really an “emplace” as it’s called in C++ land, or better yet, “try_push”.

In C++ land, emplace_back most certainly will reallocate the vector if it is currently at capacity.

Even plain emplace will reallocate as needed. It’s just insert with running the constructor in-place.

Ok, I’m confused… Why than does C++ need to have both push_back() and emplace_back()? What’s the difference?

In any case, shouldn’t there be at least some method to indicate where to push/emplace to, e.g. push_front vs. push_back?

This avoids calling an object's copy constructor or move constructor by simply constructing it in place. Since Rust doesn't have move constructors this isn't as much of an issue.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.