diff --git a/src/liballoc/boxed.rs b/src/liballoc/boxed.rs index c61e3183409f..c0a1b4a9d2a8 100644 --- a/src/liballoc/boxed.rs +++ b/src/liballoc/boxed.rs @@ -151,6 +151,43 @@ impl Box { Box(ptr.cast().into()) } + /// Allocates memory on the heap *then* constructs `T` in-place. + /// + /// If the allocation fails, the initialization closure won't be called; because the allocation + /// can fail, the optimizer often couldn't construct `T` in-place even in theory because having + /// the allocation happen first is an observable change if the code that creates the value has + /// side-effects such as being able to panic. + /// + /// FIXME: This is intended to work via return-value-optimization, but due to compiler + /// limitations can't reliably do that yet. + #[inline(always)] + fn new_in_place(f: impl FnOnce() -> T) -> Box { + let mut r: Box> = Box::new_uninit(); + let uninit: &mut mem::MaybeUninit = &mut *r; + + unsafe { + // So why aren't we using ptr::write() here? + // + // For return-value-optimization to work, the compiler would have to call f with the + // pointer as the return value. But a pointer isn't guaranteed to actually point to + // valid memory: if the pointer was invalid, eg. due to being null, eliding the copy + // would change where the invalid memory access would occur, changing the behavior in + // an observable way. + // + // An invalid reference OTOH is undefined behavior, so the compiler is free to assume + // it is valid. + // + // Unfortunately, this optimization isn't actually implemented yet. Though if + // enough of f() can be inlined the compiler can generally elide the copy anyway. + // + // Finally, this is leak free because MaybeUninit::new() can't panic: either f() + // succesfully creates the value, and assume_init() is called, or f() panics, frees its + // resources, and r is deallocated as a Box> + *uninit = mem::MaybeUninit::new(f()); + r.assume_init() + } + } + /// Constructs a new `Pin>`. If `T` does not implement `Unpin`, then /// `x` will be pinned in memory and unable to be moved. #[stable(feature = "pin", since = "1.33.0")] @@ -480,7 +517,7 @@ unsafe impl<#[may_dangle] T: ?Sized> Drop for Box { impl Default for Box { /// Creates a `Box`, with the `Default` value for T. fn default() -> Box { - box Default::default() + Box::new_in_place(T::default) } } diff --git a/src/liballoc/rc.rs b/src/liballoc/rc.rs index 2b222caf13f3..a6cade43c71d 100644 --- a/src/liballoc/rc.rs +++ b/src/liballoc/rc.rs @@ -358,6 +358,18 @@ impl Rc { } } + #[inline(always)] + fn new_in_place(f: impl FnOnce() -> T) -> Rc { + let mut r: Rc> = Rc::new_uninit(); + + unsafe { + let uninit: &mut mem::MaybeUninit = Rc::get_mut_unchecked(&mut r); + + *uninit = mem::MaybeUninit::new(f()); + r.assume_init() + } + } + /// Constructs a new `Pin>`. If `T` does not implement `Unpin`, then /// `value` will be pinned in memory and unable to be moved. #[stable(feature = "pin", since = "1.33.0")] @@ -1146,7 +1158,7 @@ impl Default for Rc { /// ``` #[inline] fn default() -> Rc { - Rc::new(Default::default()) + Rc::new_in_place(T::default) } } diff --git a/src/liballoc/sync.rs b/src/liballoc/sync.rs index 9ffc1673e5ab..683b2d6af561 100644 --- a/src/liballoc/sync.rs +++ b/src/liballoc/sync.rs @@ -338,6 +338,18 @@ impl Arc { } } + #[inline(always)] + fn new_in_place(f: impl FnOnce() -> T) -> Arc { + let mut r: Arc> = Arc::new_uninit(); + + unsafe { + let uninit: &mut mem::MaybeUninit = Arc::get_mut_unchecked(&mut r); + + *uninit = mem::MaybeUninit::new(f()); + r.assume_init() + } + } + /// Constructs a new `Pin>`. If `T` does not implement `Unpin`, then /// `data` will be pinned in memory and unable to be moved. #[stable(feature = "pin", since = "1.33.0")] @@ -1933,7 +1945,7 @@ impl Default for Arc { /// assert_eq!(*x, 0); /// ``` fn default() -> Arc { - Arc::new(Default::default()) + Arc::new_in_place(T::default) } }