Transmuting a generic array

In the example given for MaybeUnint, it is valid to std::mem::transmute() an [MaybeUninit<Vec<u32>>; 1000] to an [Vec<u32>; 1000].

This is a lot like what I need to accomplish, except in my case it's a transmute from [MaybeUninit<T>; LEN] to [T; LEN] for varying LEN (currently 0 ..= 32).

The problem is that the compiler rejects this code:

error[E0512]: cannot transmute between types of different sizes, or dependently-sized types
   --> deltoid/src/arrays.rs:43:29
    |
43  |                 Ok(unsafe { mem::transmute::<_, Self>(new) })
    |                             ^^^^^^^^^^^^^^^^^^^^^^^^^
...
132 | impl_traits_for_array!(length:  0, using:  Array0Delta);
    | -------------------------------------------------------- in this macro invocation
    |
    = note: source type: `[std::mem::MaybeUninit<T>; 0]` (size can vary because of T)
    = note: target type: `[T; 0]` (size can vary because of T)
    = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)

From the error I infer that the reason it rejects it is that T is a generic parameter, and not because of the array length.
But any time the code is actually executed, the type parameter will long since have been substituted for a concrete type. And even if it wasn't, the compiler should still be able to infer that the T in the source and target types are in fact the same. So why would the compiler have to reject that?

EDIT: I also know it can't be because of any Sized constraints, because bounding T: Sized does not compile either.

I think the following minimized example should be equivalent:

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=2419dcc353fa49a49f85c0b2a034680a

#[repr(transparent)]
struct S<T>(T);

fn unwrap<T>(s: S<T>) -> T {
    unsafe { std::mem::transmute::<S<T>, T>(s) }
}

Not sure why it errors though -- I guess it is just fundamentally different to check that T and C<T> have the same size for all Ts.

1 Like

Wouldn't any type checking system that needs to determine the size of S<T> first need to determine the size of T anyway? I believe any such system would, in general, necessarily have to do so.

Rust tries to resolve all of this kind of thing generically, before substituting concrete types. The lazy normalization tracking issue describes the effort to improve the situation.

2 Likes

To slightly rephrase what @2e71828 said, this can be trivially detected after monomorphisation, but rust tries hard to avoid monomorphisation-time errors.

1 Like

It's possible to workaround this issue by using pointer casts instead of transmutes. For example:

use std::mem;

#[repr(transparent)]
struct S<T>(T);

fn unwrap<T>(s: S<T>) -> T {
    let ptr = &s as *const S<T> as *const T;
    let value = unsafe { ptr.read() };
    mem::forget(s);
    value
}

Note that you won't get compile-time checks when doing this.

3 Likes

Thank you, this is very likely what I'll be going with, at least for now. The alternative would have been worse i.e. require T: Default and macroexpand to an array literal of the correct length full of T::default() (which is potentially expensive).

Your solution will be part of a macro expansion, so if I get it right just once it'll be good to go for all expansions.

Doesn't that mean a full copy of the T that we would not have with transmute ?

FWIW, I tried doing it with union but union fields need to be Copy which MaybeUninit is not.