Transmute doesn't work on generic types?

This:

fn foo<T, S>(t: T) -> S {
    std::mem::transmute::<T, S>(t)
}

Results in:

error[E0512]: cannot transmute between types of different sizes, or dependently-sized types
  --> src/lib.rs:66:5
   |
66 |     std::mem::transmute::<T, S>(t)
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: source type: `T` (this type does not have a fixed size)
   = note: target type: `S` (this type does not have a fixed size)

Which is strange since T and S have an implicit Sized bound. Adding an explicit sized bound to both doesn't fix the error. Why is this?

There's no guarantee that T and S will both have the same size. transmute::<u16, u32> is invalid, for example, but there's nothing to prohibit calling foo::<u16, u32>.

2 Likes

The compiler is trying to help you avoid mistakes. If two types don't have the same size, then the transmute is definitely incorrect. However, in your case, they might have the same size, or they might not. You can do it like this:

fn foo<T, S>(t: T) -> S {
    let s = unsafe { std::mem::transmute_copy::<T, S>(&t) };
    std::mem::forget(t);
    s
}

Obviously, this is only correct if they have the same size (and a bunch of other requirements).

The compiler already added an implicit Sized bound, so adding another one doesn't change anything.

The problem is not that they are not sized — the problem is that the compiler can't tell whether they have the same size.

1 Like

Hmm, but this means you're just prohibited from ever using std::mem::transmute on generic types? Is there no way to express that constraint? Transmute is after all only useful when the two types passed in are not the same... I assume?

Use transmute_copy (as demonstrated) with an assert for the size.

But this is probably an XY problem at its heart, and whatever you're doing is most likely unsound.

4 Likes

Or you can use macros instead of generics.

The sound version of foo() is bytemuck::cast().

8 Likes

Even if T and S have the same size, transmuting between them in general is definitely unsound. For transmute to be possible, every bit representation of T must be a valid bit representation of S. This is not true if e.g. T = u8 and S = bool, since bool can have only values 0 and 1. Transmuting a value which is not a valid value of target type is instant UB.

This means that the thing you're trying to do is fundamentally impossible to do soundly, and likely shouldn't be done in the first place. transmute is super dangerous, and super rare to see in Rust code. What is the problem which makes you want to have a generic wrapper over transmute?

Note that with proper trait bounds you can specify the types, which can be transmuted safely. bytemuck linked above does exactly that. But this requires carefully going over the possible types, checking that they can be transmuted, and providing a proper conversion function (e.g. it is invalid to transmute Vec<T> to Vec<S>, even if T and S are transmute-compatible, since the field layout of Vec is unspecified, and can differ between different instantiations).

2 Likes

Of late I've started suggesting

unsafe fn foo<T, S>(t: T) -> S {
    std::mem::transmute_copy::<ManuallyDrop<T>, S>(&ManuallyDrop::new(t))
}

To follow the usual "prefer pre-leaking to post-forgetting" guidance. (Just to be consistent about it -- in this case the forget approach is also totally fine.)

And, conveniently, it's a one-liner this way.

7 Likes

That's kind of neat!

1 Like

In this case I was transmuting between the same physical type, just with different lifetimes, so there was no danger of illegal bit representations. I've read the docs for transmute, I know the risks :slight_smile:

What version of rust are you using?

You should be able to just do this as of 1.66: https://github.com/rust-lang/rust/pull/101520

Latest stable, but in generic code where the compiler can't see that T and S are the same except lifetime. E.g. called in a function that looks like:

fn foo<T,S>(t: T) -> S