Is it safe to transmute a generic array of non-copy type?

There is well knows example in documentation for Initializing an array element-by-element.

They use std::mem::transmute for converting [MaybeUninit<Vec<u32>>; 1000] into [<Vec<u32>; 1000]

Although It work but transmute don't like generic, There is a issue about Generic array transmutes do not work

For example, This doesn't work!

use core::mem::{transmute, MaybeUninit};
fn default_array<T: Default + Sized, const N: usize>() -> [T; N] {
    unsafe {
        let mut arr: [MaybeUninit<T>; N] = MaybeUninit::uninit().assume_init();
        for ele in arr.iter_mut() {
            *ele = MaybeUninit::new(T::default());
        }
        transmute(arr)  // cannot transmute between types of different sizes, or dependently-sized types
    }
}

In that Issue #61956, They mention a workaround like this:

// Using &mut as an assertion of unique "ownership"
let ptr = &mut arr as *mut _ as *mut [T; N];
let res = unsafe { ptr.read() };
core::mem::forget(arr);
res

My question is, "Does this horribly solution also work for Non-Copy type ?"

As I know that, ptr::read, Copy the contain without moving it. Something like mem::transmute_copy

Yes it does. One example is the unstable method array_assume_init in the standard library itself.

impl<T> MaybeUninit<T> {
    pub unsafe fn array_assume_init<const N: usize>(array: [Self; N]) -> [T; N] {
        // SAFETY:
        // * The caller guarantees that all elements of the array are initialized
        // * `MaybeUninit<T>` and T are guaranteed to have the same layout
        // * `MaybeUninit` does not drop, so there are no double-frees
        // And thus the conversion is safe
        unsafe {
            intrinsics::assert_inhabited::<[T; N]>();
            (&array as *const _ as *const [T; N]).read()
        }
    }
}

(ignore the assert_inhabited thing, I don’t think it does anything important)

This doesn’t mean that your default_array method will work as intended this way. It will still misbehave if any of the T::default calls panics! (Resulting in the already-constructed values to be leaked.) But don’t worry, no need to invent something new here when the standard library has simpler (and safe) solutions…

The way to implement a default_array method, and one that actually already works on stable is to use the map method of arrays:

fn default_array<T: Default + Sized, const N: usize>() -> [T; N] {
    [(); N].map(|()| T::default())
}

Rust Playground

Once that’s stabilized, the array::from_fn method could be used instead:

fn default_array<T: Default + Sized, const N: usize>() -> [T; N] {
    std::array::from_fn(|_| T::default())
}

Rust Playground

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.