Using MaybeUninit with arrays

I noticed the recent implementation of std::mem::MaybeUninit in rust nightly, and I wanted to make some practice.

I am stuck with the idea of using MaybeUninit for array incremental initialization (it can be useful for non-Copy objects). The idea was to create an array of MaybeUninit::uninitialized, but MaybeUninit is not Copy (therefore we cannot write [MaybeUninit::uninitialized(), 10]).
Even in the case that impl<T> Copy for MaybeUninit<T> where T: Copy (which is not from the source, don’t know if it is intended), what I wanted to do would be impossible for non-Copy objects.
On the other hand, it should be theoretically safe and sound to copy a uninitialized MaybeUninit, isn’t it? It is obvious to me that this could be not easy at all, because it would need to track down the initialization status at compile time, in order to avoid the Copy trait for initialized MaybeUninit<T> where T is not Copy.

There is another point a bit obscure to me. Imagine that I successfully built an array of initialized MaybeUninit<T>. Is there a way to convert it to an array of T? AFAIK, at to date it is not possible to const collect from an iterator with a known const size. Or, looking at the problem from another point of view, we don’t have a ConstExactSizeIterator. Do you think that it would be possible to do the thing in another way?

Observations? Suggestions? As I said, I was just testing the possibilities with this new feature, nothing related to real cases (for now :wink:) . I would like to discuss a bit what can and cannot be done with the current implementation.

Interesting enough, an issue has been opened 3 days ago with a similar question.

It seems like uninitialized arrays would be an intended use case for MaybeUninit, so I wonder if it’s an oversight that MaybeUninit is not Copy when T: Copy

1 Like

Okay, so according to some new posts in the issue you linked, you can do MaybeUninit<[T; N]> and that’s basically the same thing as [MaybeUninit<T>, N].

In other words, it’s distributive kinda like how A(B + C) == AB + AC.

1 Like

Well, MaybeUninit::uninitialized is const fn, so the long-term fix here is probably to allow const fn in array expressions even when non-Copy, and then [MaybeUninit::uninitialized(), 10] will just work. (I thought there was an RFC for that, but I can’t seem to find it.)

In the mean time, you’re using unsafe if you’re using MaybeUninit anyway, so I think you could just transmute from MaybeUninit<[T;N]> to [MaybeUninit<T>; N] for now. (And it might be worth trying out in unstable some unsafe associated functions on MaybeUninit for things like this, or &[MaybeUninit<T>] -> &[T].)

2 Likes

Often just things like:

[vec![]; 50]
[HashMap::new(); 50]

1 Like

The only things that worries me is that if an unwind occurs when some elements are initialized, we could leak resources (EDIT: actually, the problem exists for both, so yes, they are almost the same thing). Don't get me wrong: it is an huge improvement respect to mem::uninitialized(), it looks like there is not (easy) way to trigger UB when panic happens.

Unfortunately my conclusion has been the same. The idea is, if we can say that, that using unsafe with MaybeUninit is less unsafe that using mem::uninitialized. Probably the amount of unsafe code does not change so much, but it is easier to handle corner cases.

Just an idea: what about some specific impl<T> MaybeUninit<[T; $n]> using macros? Could this be helpful?

Ok, I was thinking about one helper, which does not involve array specialization, but the following:

impl<T, Idx> Index<Idx> for MaybeUninit<T>
where
    T: Index<Idx>,
    <T as Index<Idx>>::Output: Sized,
{
    type Output = MaybeUninit<<T as Index<Idx>>::Output>;

    fn index(&self, index: Idx) -> &Self::Output {
        unsafe { ::std::mem::transmute(&self.value[index]) }
    }
}

Do you think that this could be useful?
EDIT: nope, this is unsound for things like vec, as noted by @scottmcm.

I’m pretty sure that’s unsound, since it lets me do MaybeUninit::<Vec<T>>::uninitialized()[0] in safe code.

1 Like

Yes, you are right.

This reminds me of Cell::as_slice_of_cells, which (under the as_cell feature gate) allows converting a &Cell<[T]> to a &[Cell<T>]. I feel the same approach ought to work here, with an addition to the API:

impl<T> MaybeUninit<[T]> {
    pub fn as_slice_of_uninits(&mut self) -> &mut [MaybeUninit<T>] {
        unsafe { &*(self as *mut _ as *mut _) }
    }
}

I’m pretty sure this would be safe. You could use it like this:

let array: MaybeUninit<[String; 2]> = MaybeUninit::uninitialized();
{
    let r: &mut MaybeUninit<[String]> = &mut array;
    let s = r.as_slice_of_uninits();
    s[0].set("hello".into());
    s[1].set("world".into());
    // set all the other values...
}
let array = array.into_inner();

Unfortunately this can’t work as of right now because unions can’t have dynamically sized fields. I’m not sure what the reason is for this rule; it seems like they wouldn’t have any layout issues that can’t be solved the same way dynamically sized structs work, but I haven’t thought long and hard about it.

2 Likes