Skip to content

Commit b0674d3

Browse files
committed
Attempt to make Box::default() construct in-place
1 parent 59cc53e commit b0674d3

File tree

1 file changed

+38
-1
lines changed

1 file changed

+38
-1
lines changed

src/liballoc/boxed.rs

+38-1
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,43 @@ impl<T> Box<T> {
151151
Box(ptr.cast().into())
152152
}
153153

154+
/// Allocates memory on the heap *then* constructs `T` in-place.
155+
///
156+
/// If the allocation fails, the initialization closure won't be called; because the allocation
157+
/// can fail, the optimizer often couldn't construct `T` in-place even in theory because having
158+
/// the allocation happen first is an observable change if the code that creates the value has
159+
/// side-effects such as being able to panic.
160+
///
161+
/// FIXME: This is intended to work via return-value-optimization, but due to compiler
162+
/// limitations can't reliably do that yet.
163+
#[inline(always)]
164+
fn new_in_place(f: impl FnOnce() -> T) -> Box<T> {
165+
let mut r: Box<mem::MaybeUninit<T>> = Box::new_uninit();
166+
let uninit: &mut mem::MaybeUninit<T> = &mut *r;
167+
168+
unsafe {
169+
// So why aren't we using ptr::write() here?
170+
//
171+
// For return-value-optimization to work, the compiler would have to call f with the
172+
// pointer as the return value. But a pointer isn't guaranteed to actually point to
173+
// valid memory: if the pointer was invalid, eg. due to being null, eliding the copy
174+
// would change where the invalid memory access would occur, changing the behavior in
175+
// an observable way.
176+
//
177+
// An invalid reference OTOH is undefined behavior, so the compiler is free to assume
178+
// it is valid.
179+
//
180+
// Unfortunately, this optimization isn't actually implemented yet. Though if
181+
// enough of f() can be inlined the compiler can generally elide the copy anyway.
182+
//
183+
// Finally, this is leak free because MaybeUninit::new() can't panic: either f()
184+
// succesfully creates the value, and assume_init() is called, or f() panics, frees its
185+
// resources, and r is deallocated as a Box<MaybeUninit<T>>
186+
*uninit = mem::MaybeUninit::new(f());
187+
r.assume_init()
188+
}
189+
}
190+
154191
/// Constructs a new `Pin<Box<T>>`. If `T` does not implement `Unpin`, then
155192
/// `x` will be pinned in memory and unable to be moved.
156193
#[stable(feature = "pin", since = "1.33.0")]
@@ -480,7 +517,7 @@ unsafe impl<#[may_dangle] T: ?Sized> Drop for Box<T> {
480517
impl<T: Default> Default for Box<T> {
481518
/// Creates a `Box<T>`, with the `Default` value for T.
482519
fn default() -> Box<T> {
483-
box Default::default()
520+
Box::new_in_place(T::default)
484521
}
485522
}
486523

0 commit comments

Comments
 (0)