Description
Assigning MaybeUninit::<Foo>::uninit()
to a local variable is usually free, even when size_of::<Foo>()
is large. However, passing it for example to Arc::new
causes at least one copy (from the stack to the newly allocated heap memory) even though there is no meaningful data. It is theoretically possible that a Sufficiently Advanced Compiler could optimize this copy away, but this is reportedly unlikely to happen soon in LLVM.
This issue tracks constructors for containers (Box
, Rc
, Arc
) of MaybeUninit<T>
or [MaybeUninit<T>]
that do not initialized the data, and unsafe conversions to the known-initialized types (without MaybeUninit
). The constructors are guaranteed not to make unnecessary copies.
PR #62451 adds:
impl<T> Box<T> { pub fn new_uninit() -> Box<MaybeUninit<T>> {…} }
impl<T> Box<MaybeUninit<T>> { pub unsafe fn assume_init(self) -> Box<T> {…} }
impl<T> Box<[T]> { pub fn new_uninit_slice(len: usize) -> Box<[MaybeUninit<T>]> {…} }
impl<T> Box<[MaybeUninit<T>]> { pub unsafe fn assume_init(self) -> Box<[T]> {…} }
impl<T> Rc<T> { pub fn new_uninit() -> Rc<MaybeUninit<T>> {…} }
impl<T> Rc<MaybeUninit<T>> { pub unsafe fn assume_init(self) -> Rc<T> {…} }
impl<T> Rc<[T]> { pub fn new_uninit_slice(len: usize) -> Rc<[MaybeUninit<T>]> {…} }
impl<T> Rc<[MaybeUninit<T>]> { pub unsafe fn assume_init(self) -> Rc<[T]> {…} }
impl<T> Arc<T> { pub fn new_uninit() -> Arc<MaybeUninit<T>> {…} }
impl<T> Arc<MaybeUninit<T>> { pub unsafe fn assume_init(self) -> Arc<T> {…} }
impl<T> Arc<[T]> { pub fn new_uninit_slice(len: usize) -> Arc<[MaybeUninit<T>]> {…} }
impl<T> Arc<[MaybeUninit<T>]> { pub unsafe fn assume_init(self) -> Arc<[T]> {…} }
PR #66128 adds:
impl<T> Box<T> { pub fn new_zeroed() -> Box<MaybeUninit<T>> {…} }
impl<T> Arc<T> { pub fn new_zeroed() -> Arc<MaybeUninit<T>> {…} }
impl<T> Rc<T> { pub fn new_zeroed() -> Rc<MaybeUninit<T>> {…} }
Unresolved question:
- The constructor that returns for example
Box<MaybeUninit<T>>
might “belong” more as an associated function of that same type, rather thanBox<T>
. (And similarly for other constructors.) However this would make a call likeBox::<u32>::new_uninit()
becomesBox::<MaybeUnint<u32>>::new_uninit()
which feels unnecessarily verbose. I suspect that this turbofish will be needed in a lot of cases to appease type inference.
Activity
#![feature(maybe_uninit_slice)]
#63569TimDiekmann commentedon Oct 5, 2019
Just from looking at the current source code:
When
mem::size_of::<T>() == 0
, a zero-sized layout is passed toGlobal.alloc
butalloc
mentions:Did I have overseen something or is this a bug?
SimonSapin commentedon Oct 5, 2019
Good point! I copied this pattern from
Rc
andArc
, but there the header (refcounts) cause the allocation to never be zero-size.Box::new_uninit_slice
has a similar bug withsize_of() == 0
orlen == 0
.TimDiekmann commentedon Oct 5, 2019
I have noticed this when implementing
Box
for custom allocators with non-zero layouts. Turns out, that this is indeed useful 🙂This is my implementation for the sliced version.
Fix zero-size uninitialized boxes
159 remaining items