can't transmute array of MaybeUninit<T> to [T; _] for generic T

I ran into tihs error and don't know how to solve it:

error[E0512]: cannot transmute between types of different sizes, or dependently-sized types
   --> src/path/to/my/code.rs:155:50
    |
155 |         let xs: [T; 16] = unsafe { std::mem::transmute(xs) };
    |                                                  ^^^^^^^^^^^^^^^^^^^
    |
    = note: source type: `[MaybeUninit<T>; 16]` (size can vary because of T)
    = note: target type: `[T; 16]` (size can vary because of T)

In this instance, I was converting code that previously created an fixed-size array of a specific type to instead create an array of a parameterized type, which has a trait defined in my crate. (The trait has several associated types, and the impl has several where clauses relating to those associated types).

In other words, this was just working a second ago, but now that it's a parameterized type, it won't work. I put a Sized requirement on the trait and the relevant associated types, but to no avail.

Apologies I can't share more of the code. Any ideas on what the cause of this is? It is my second time using MaybeUninit for the purposes of filling in a fixed size array. In this case I switched the code to use Vec, and got around the issue. It's not critical to use a fixed size array in my case, but I generally prefer it for various reasons.

6 comments · 87% upvoted · other discussions

comments

mbrubeck

mem::transmute has some known limitations involving arrays of generic types. You can work around this by using raw pointers instead:

unsafe fn f<T>(array: [MaybeUninit<T>; 16]) -> [T; 16] {
    array.as_ptr().cast::<[T; 16]>().read()
}
jstrong [OP]

this worked, thank you!

C5H5N5O

See /u/mrbrubeck's comment about the known limitations of mem::transmute. You can workaround this by using mem::transmute_copy:

fn test<T: Copy + Default, const N: usize>() {
    let mut a: [MaybeUninit<T>; N]= MaybeUninit::uninit_array::<N>();
    a.iter_mut().for_each(|x| *x = MaybeUninit::new(Default::default()));
    // let b = unsafe { transmute::<_, [T; N]>(a) };
    let c = unsafe { transmute_copy::<_, [T; N]>(&a) };
}

Note that you will invoke UB when src and dst type aren't equally sized and don't have the same memory layout ([MaybeUninit<T>; N] -> [T; N] should be safe though).

jstrong [OP]

had never come across transmute_copy, which I take it is like transmute but into a second object without touching the first one.

from the docs of transmute_copy

It is not a compile-time error if T and U have different sizes, but it is highly encouraged to only invoke this function where T and U have the same size.

hard for me to imagine what the circumstances would be for intentionally invoking it on types of different sizes. seems like it would have to be an exotic problem you are facing.

Lucretiel

I imagine it's for stuff like "unsafely write a value directly into a union"

I also used it in my stowaway crate, which was designed to "pack" a value into a pointer if it fits in the space (1 word, 4 or 8 bytes), and box it otherwise. Sadly it ended up being unsound in ways I couldn't resolve for (surprisingly) unrelated reasons (related to the treatment of padding / uninit bytes in nonprimitive types)

YatoRust

Sadly it ended up being unsound in ways I couldn't resolve

Sorry about that