I'd like some feedback on an RFC I wrote to add a macro to the standard library that abstracts over box syntax, and perhaps future methods of initializing memory on the heap.
From what I recall, the problem with "placement new" is not its syntax, it's that there doesn't currently seem to be a way to actually provide meaningful guarantees about where the data is constructed, so even if you use box syntax, the data might be constructed on the heap stack first.
Several changes to placement new have been proposed, but none of them address this issue.
To clarify my point above: If the new language feature does not add any hard guarantees, then it might as well just be an optimization the compiler performs opportunistically (we do have sufficient wiggle-room in the language semantics for this).
Implementing this optimization would be much less involved than going through the whole RFC process, and would provide valuable data points about whether we even need a placement new language feature. It would also likely be a prerequisite for any language-level feature.
wiggle-room
Never underestimate the impact of Big-Library not compiling without such an optimization
Why is it not possible to provide meaningful gurantees? Iirc C++ has had emplacement for a while now.
C++'s guarantees are defined in terms of which constructors (including copy/move constructors) are or are not called, which is an observable behavior from within the program.
Rust does not have constructors of any kind, or really anything that makes moves observable in that way. So one problem is figuring out a way to specify those guarantees. But even given a solution to that problem, idiomatic Rust also includes a lot of things that C++ never has to deal with- e.g. what if you want to place a value of type T
constructed by a function returning Result<T, E>
? This requires some new ABI functionality that does not exist anywhere yet.
(Nobody is saying that this is definitely not possible, just that none of the proposals so far have accomplished it.)
I'd like to propose my own solution here.
Step A:
Add a method to Default
trait:
trait Default {
....
fn init_with_default(uninit: &mut MaybeUninit<Self>) {
uninit.write(Self::default());
}
}
Step B:
Enhance derive(Default)
to provide a field-by-field initialization implementation for this method. Also switch its current implementation for fn default() -> Self
method to calling this method on a MaybeUninit
value to initialize.
Step C:
Change impl<T> Default for Box<T>
's default
implemenation to:
impl<T> Default for Box<T> where T: Default {
fn default() -> Self {
let mut v = Box::<T>::new_uninit();
T::init_with_default(v.as_mut_ptr());
unsafe {
v.assume_init()
}
}
}
With this new implementation, initializing memory on the heap is promised. You can also do custom initialization steps by writing code similiar to the code in Step C, without relying on the box syntax at all.
This method would need to be on an unsafe trait for it to promise to fully initialise the input parameter. I’m not sure if there’s a safe way you could guarantee that it has been initialised with field-by-field initialisation
Specialization?
unsafe trait DefaultInitialize : Default {
default fn default_initialize(self: &mut MaybeUninit<Self>) {
self.write(Self::default);
}
}
default impl<T: Default> DefaultInitialize for T {}
// #[derive(Default)] implements both
Super error prone for manual implementation, of course, but it's unsafe
and no worse than unsafe to implement on the Default
trait, so it's fine, right?
Maybe even with super specialization powers you could add
default impl<T: DefaultInitialize> Default for T {
default fn default() -> Self {
let mut this = MaybeUninit::uninit();
Self::default_initialize(&mut this);
unsafe { this.assume_init() }
}
}
without overlaps?
(Not necessarily saying this is a good idea, just theoretically possible)
Removed mentions of placement new syntax, focusing more on the fact that it's "magic" syntax.
This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.