Safety of dropping an uninitialized array of `T: Copy`?

Happy new year :crab:,

It's obvious that dropping arr as below will cause undefined behavior,
since the uninitialized arr will be considered as a valid String array and destructor of String will be invoked on each uninitialized element of arr.

let arr: [String; 4] = unsafe {
    std::mem::MaybeUninit::uninit().assume_init()
};
std::mem::drop(arr);

My main question is:

Would dropping an uninitialized array of T where T: Copy lead to undefined behavior?

let arr: [u64; 4] = unsafe {
    std::mem::MaybeUninit::uninit().assume_init()
};
std::mem::drop(arr);

T: Copy cannot implement Drop. Thus when a [T; 4] is dropped,
I expect that no access will be made on T,
and the stack memory occupied by the array will simply be retrieved.

My feelings tell me that dropping an uninitialized array of T: Copy wouldn't lead to undefined behavior, but I'm not sure...

The problem is in uninit().assume_init(), even if you leaked the values to never drop. It might not actually appear to cause problems (yet), but this is specified as UB in the docs:

It is up to the caller to guarantee that the MaybeUninit<T> really is in an initialized state. Calling this when the content is not yet fully initialized causes immediate undefined behavior.

4 Likes

As a common counter example, consider that &u8 is Copy and must be nonnull, which is violated by it being uninit. It's still an open question whether scalar types like integers and floats can be uninit. Until that is resolved it should be treated as UB

3 Likes

According to Behavior considered undefined behavior, it is already well defined that creating a stack array of uninitialized integers is definitely UB, because this qualifies as producing an invalid value.

Producing an invalid value, even in private fields and locals. "Producing" a value happens any time a value is assigned to or read from a place, passed to a function/primitive operation or returned from a function/primitive operation. The following values are invalid (at their respective type):

  • An integer (i*/u*), floating point value (f*), or raw pointer obtained from uninitialized memory, or uninitialized memory in a str.

As I understood this text, the thing that is merely not decided yet is whether it is UB to create a reference of type integer that points to uninitialized memory.

I may be wrong of course.


Of course, as the other notes, even with the strings, it is not the dropping that causes problems, but the creation of the invalid values.

2 Likes

Yes, right now it is UB. The discussion I linked is about if it should be UB, which is still undecided.

Quoting @RalfJung's opening comment here

The remaining open question is: is it ever allowed to have an uninitialized bit in an integer or floating point value? We could reasonably decide either way. Also, when an integer is partially uninitialized, does that "infect" the entire integer or do we exactly preserve which byte is initialized?

2 Likes