I recently had occasion to need placement new which sent me down the rabbit hole of reading the older and newer RFCs.
If the goal is to start with the most minimal RFC, I think it's possible to go even more minimal than the current RFC (leaving GCE for later) and still have a useful feature, and looking at the old RFC and the new one it seems like people may have been too eager to reject things on the basis of not solving everything at once. PoignardAzur is basically being asked by everybody to simultaneously solve the problem that rust doesn't have variadics/perfect-forwarding and add copy and move ellision rules. While high standards are good I think the desire to solve everything at once is blocking consensus.
C++ had placement new and users getting lots of value out of it for over 10 years before variadic templates and perfect forwarding were added and made it possible to generically/safely wrap construction. Placement new was in C++98, and originally there was no way to implement operations like emplace
on vectors, and despite that it was still hugely useful for writing highly efficient code.
If Rust added C++ placement new as is, literally something like:
let foo: *mut Foo = unsafe { Foo (buffer) { field: make_value()? }; }
Equivalent to:
auto p = new (buffer) Foo(make_value());
What would this prevent adding in the future? Syntax can be bike shedded of course. The problem of wrapping generically and being able to support things like emplace
for Vec
items could be handled in later RFCs.
With this sort of approach all the concerns caused by closures and fallibility go away. break
, continue
, and the ?
work exactly as one would expect. Likewise you are not left hoping the compiler inlines the closure calls you want it too (see my comment on the current RFC). I can't speak for serde/mio authors, and I have not tried writing a zero copy serialization framework in Rust, but I have written one in C++ before and this was as much as I needed. When you're iterating fields at compile time generating separate code for each struct you're serializing anyway it's not a big deal to generate a function that does the specific placement new you need and the problem of variadics and perfect forwarding can mostly be ignored.
With this approach the fact that you are doing a placement new is explicit by virtue of having a slight change in syntax in order to specify the buffer, and users don't need to memorize any rules to figure out whether or not placement new will actually happen. As far as I can tell having this as a built-in language feature wouldn't prevent adding GCE, or variadics, or safe wrapping APIs. I have not specifically investigated but I assume it also pretty trivially maps to LLVM. I'll resist the urge to speculate about what the implementation effort in rustc would be but I have to assume that it's less than any other proposal because it doesn't require the compiler to do anything smart.
Maybe if buffer
is a &mut [u8]
we can have placement new give back &mut Foo
instead of *mut Foo
where the lifetime is inherited from buffer
. But the operation is going to have to be unsafe anyway because we don't know if we are clobbering some other object, and in the kind of context where you are likely to use placement new you are probably going to immediately turn a reference into a pointer anyway.
If we really wanted to bikeshed syntax, for the first version could just expose a variadic macro placement_new!(buffer, Foo, arg1, arg2);
that has to be imported and that is directly implemented by the compiler. Then even downstream tools like rustfmt, syn/parse, etc. don't have to lift a finger.