Another idea for placement new

It can't take ownership of an existing place, but if you exclusively use the library approach you can create stack slots holding MaybeUninits and initialize them separately, just like moveit does (though moveit was not the crate I had in mind).

Yeah this is where it gets ugly. You need macros to generate code specific to reach struct.


Also AFAIK it's not possible right now to initialize an enum in place because there's no stable way of writing its discriminant.

Ah yeah, with my C++ background I assumed this would be combined with some form of return value optimisation. That would indeed be needed, and I assumed rust did thst already.

I assume this is for when p points to something outside the local scope (otherwise the non-initialised value wouldn't matter, since we are doing early return via the question mark). Maybe something like the reverse of a drop flag to indicate when it is intialised? Most of the time such a flag wouldn't be needed and could be optimised away.

While this works in full generality, it's not particularly nice as a suggested solution since it needs unsafe to call assume_init. &mut MU<T> -> &move T does resolve that problem, but at the expense of losing the ability to initialize a field of a larger place using that same interface.

I've had an itch to implement my own alternative to moveit (tentatively called pinit) for a while; maybe it'd be possible to address if the low level function is &mut MU<T> -> &mut T and build from there for -> &move T and piecewise initialization utilities[1].

This is the case for repr(Rust) enums. But repr(C) and repr(C, int) enums have a defined stable layout, and a main part of the motivation was enabling the manual assembly of values (e.g. in C, but also Rust).

Rust does RTO sometimes (when LLVM feels like it) but NRVO is generally rare[2], effectively requiring inlining. The problem is that as an optimization, RVO doesn't happen in unoptimized compilation, causing some code to have massively different stack usage in debug versus release, which isn't great.

This is exactly isomorphic to what drop flags are/do. Drop flags are already used for this when you use a conditionally initialized binding (e.g. [3]). The bool polarity of the flag doesn't matter, it's isomorphic in either configuration. This is what the moveit crate does, to ensure pinned values get dropped before they're deallocated.

But the actual issue is that there's nothing that forces foo()? to write to *p iff it returns Ok(_) if it uses a source level out reference. So the caller still can't use *p, even if the hidden drop flag means that the value will get dropped. It's exactly the same conditional init scenario as you can do safely today with let name;.

It's necessary to remember that any such assertion relies on inlining. Currently, drop flags are fully local to a single function body, so are trivially removed of unnecessary. Crossing function boundaries makes this difficult; LLVM doesn't ever modify function ABI even if it could theoretically “partially inline” the body to shim to a more efficient calling convention. Rustc is the one applying extern "Rust" ABI optimizations.


  1. My main thought being that by dropping support for Pin<&move T>, the manual drop flags shouldn't be needed. I want to see that worked out. Although in place init isn't as useful when it isn't pinned already… ↩︎

  2. NRVO is observable since the NRV address isn't the RVO address. (By LLVM semantics; Rust's opsem currently doesn't guarantee this address disjointness here in at least one model, so NRVO is possible to do in theory.) If LLVM can't prove you don't ever observe the address, it can't legally do NRVO (its semantics). ↩︎

  3. let name;
    if condition() {
        name = value;
    }
    // name can't be used or initd
    // name drops at scope exit
    
    ↩︎
2 Likes

Why is that a problem? The optimiser is supposed to make the code use less resources over all (be it time or memory, or a tradeoff between those two). It is the equally a problem that Rust is extremely slow in debug builds (sometimes to the point of not being usable without enabling optimisations for certain crates).

Hm, could rust add a custom llvm optimisation pass for this purpose? We would want to get into to upstream to avoid having to have a patched llvm of course, and it would be some significant effort.

I'm sometimes doing stuff similar to that:

fn init(s: &mut {} MyStruct) -> &mut MyStruct {
    // initialize
    unsafe { core::mem::transmute(s) }
}

It's not a problem per say for RVO to be limited to an optimization, but anyone discussing placement is generally looking for something more guaranteed than an optimization that might not apply based on the whims of LLVM. Performance limitations absent optimizations are usually somewhat ubiquitous in a compute-intensive library. Stack overruns due to lack of RVO are usually randomly dotted in code that is otherwise not an issue to run in debug mode. I guess you could say RVO is an opt-level=½ kind of thing, if you really wanted to not guarantee doing it at level 0.

Optimizations that change ABI are scary, because if one side of the ABI does it and the other doesn't, then you have unleashed the nasal demons (UB). In form of adding alternative entry points, though, it's at least tamable as a combination of outlining and inlining.

Swift has specific swiftcc in LLVM, so it's certainly possible to add a rustcc if we want, but LLVM can justify any ABI opts that don't change public symbols that it wants to (in fact Rust v0 name managing has a clause specifically for LLVM generating alternative versions of a function symbol), but it's rare to see it done between CGUs and not only on private symbols.

Transmuting &mut isn't great practice, even if it's only casting between sized layout-equivalent types. IIRC it's fine in all of the proposed opsem models, but in theory it could be UB for similar reasons that UB is allowed for pointer/int transmutes — weird as of yet unspecified provenance behaviors.

The correct way to do this cast is &mut *(s as *mut _ as *mut _) or ptr::from_mut(s) .cast() .as_mut() .unwrap_unchecked() depending on which style you prefer (and if type inference accepts the methods).

2 Likes