Safety of transmute

Is this code safe?

use std::marker::PhantomData;
struct Foo<T> {
    bar: u8,
    baz: PhantomData<T>,
}
impl<T> Foo<T> {
    fn new(bar: u8) -> Self {
        Self { bar, baz: PhantomData }
    }
}

fn main() {
    let v = Foo::<&mut i32>::new(4);
    let x = unsafe {
        // don't focus on i32 and bool, I've chosen two random types
        std::mem::transmute::<Foo<&mut i32>, Foo<&mut bool>()
    };
}

Formally, there is no guarantee that Foo<&mut i32> and Foo<&mut bool> resemble each other at all. But this is easy to fix: just add #[repr(C)] or #[repr(transparent)].

#[repr(C)]
struct Foo<T> {

You should also be careful when actually using the transmute that you keep the lifetime that the references have the same, unless you intend to change it. Do this by specifying the lifetime explicitly on both sides, using an explicitly declared lifetime parameter.

fn change_foo_mut_ref_type<'a>(f: Foo<&'a mut i32>) -> Foo<&'a mut bool> {
    unsafe { std::mem::transmute::<Foo<&'a mut i32>, Foo<&'a mut bool>>(f) }
}
3 Likes

While it’s generally best to default to preserving lifetimes exactly, the lifetime isn’t important in that trivial Foo case. Conversely, conceivably, Foo could use the i32 vs bool generic as… idk… typestate or something. That is, a hypothetical version of Foo could make assumptions about its generic parameter which make it unsound to transmute between different versions of Foo; the semantic meaning of the value could change in a way that invalidates some of the assumptions made by Foo’s (hypothetical) unsafe code, even if the bitwise representation is valid.

1 Like

the lifetime isn’t important in that trivial Foo case. Conversely, conceivably, Foo could use the i32 vs bool generic as… idk… typestate or something.

I don't see why we should assume that the lifetime isn't important, if the equally-phantom referent type is. Both depend on how this type is actually being used. Perhaps a better statement of my point would be “are you sure you want to also transmute the lifetime, which is hidden?” It might be the right choice, but you should be sure about both parts, and write them explicitly to document what you're doing.

3 Likes

Yeah, I'm just talking about hypotheticals. I wanted to clarify that the lifetime and/or referent may or may not matter, independently of each other.

I wish there was a way in the standard library to only transmute the type, or only transmute the lifetime(s). While I have wanted to do either I've never wanted to do both at once.

This does get a bit complicated though once you have something like &'a Foo<'b>...

You can't because lifetimes and generics are part of the type itself