Compile errors by `transmute` and `PhantomData`

I am not good at English. Sorry if there are any funny expressions.


I must use transmute, unfortunately.
On top of that, I encountered an strange error.
I can't understand why this results in an error.

Test code

Compiling the following code ...

use std::{marker::PhantomData, mem};

fn conv<T, U1, U2>(src: MyType<T, U1>) -> MyType<T, U2> {
    unsafe { mem::transmute::<MyType<T, U1>, MyType<T, U2>>(src) }
}

#[repr(transparent)]
pub struct MyType<T, U> {
    fld1: T,
    fld2: PhantomData<U>,
}

... result in the following error.

error[E0512]: cannot transmute between types of different sizes, or dependently-sized types
 --> src\lib.rs:4:14
  |
4 |     unsafe { mem::transmute::<MyType<T, U1>, MyType<T, U2>>(src) }
  |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = note: source type: `MyType<T, U1>` (size can vary because of T)
  = note: target type: `MyType<T, U2>` (size can vary because of T)

My thought process

I know that transmute requires the input and output types to be the same size.

In this case, the input type is MyType<T, U1> and the output type is MyType<T, U2>. Here, the size difference between U1 and U2 can be ignored by PhantomData.

As an experiment, I removed the type fields and tried MyType<T> and MyType<T>, as well as MyType<U1> and MyType<U2>. Both compiled successfully. Is there a problem with combining normal types and PhantomData?

I guess that this is a shortcoming of the compiler not realizing, that the size of U does not matter for MyType, since it's only PhantomData, which only exists at compile time.

I assume that you do not have access to the internal fields, such that this is not possible for you?

use std::marker::PhantomData;

fn conv<T, U1, U2>(src: MyType<T, U1>) -> MyType<T, U2> {
    MyType { fld1: src.fld1, fld2: PhantomData }
}

#[repr(transparent)]
pub struct MyType<T, U> {
    fld1: T,
    fld2: PhantomData<U>,
}
1 Like

You get the object by value, so you don't need unsafe for this one:

fn conv<T, U1, U2>(src: MyType<T, U1>) -> MyType<T, U2> {
    MyType { fld1: src.fld1, fld2: PhantomData }
}

This should optimize away to nothing.

If you had the same by reference, then you could cast the pointers.

1 Like

it seems so, and I think your example is the same as in this issue:

apparently the size checking process for transmute() has some corner cases.

the workaround is to use transmute_copy() instead, which doesn't check the size of src and dst, just remember to wrap src in ManuallyDrop first.

3 Likes

Thank you for your replies.

@Schard, @kornel: Indeed, I can access the internal fields. So, as you both suggested, I can use the internal fields for initialization without using transmute. The actual program has many type parameters, and I overlooked the simplest method. So, when I first wrote “I must use transmute,” it was a bit of a lie. I apologize for that.

@nerditation: Oh, so it was a known issue. I looked for it too, thinking it might be a bug, but couldn't find it. I was confused, so this was very helpful.

Thank you all so much.

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.