Pre-RFC / discussion: Legal transmute between repr(Rust) with repr(transparent) members

This isn't possible in general as worded. Counterexample adapted from this thread [playground]:

trait Foo {
    type Bar;
}

struct S(u8);
#[repr(transparent)]
struct SS(S);

impl Foo for S {
    type Bar = u8;
}

impl Foo for SS {
    type Bar = u128;
}

#[repr(transparent)]
struct Baz<T: Foo>(T::Bar, std::marker::PhantomData<T>);

fn hole<T>() -> T {
    panic!()
}

fn test() {
    let s: Baz<S> = hole();
    let ss: Baz<SS> = unsafe { std::mem::transmute(s) };
    // ^ cannot transmute between types of different sizes
}

In this example, S and SS are repr(transparent)-equivalent. However, even though Baz<T> is itself #[repr(tranparent)], Baz<S> and Baz<SS> are not repr(transparent)-equivalent, as the container can actually contain any associated data.

To make this even more startling, this probably won't even need a trait bound at some point in the future:

#[repr(transparent)]
struct Baz<T>([u8; {std::any::type_name::<T>().len()}], std::marker::PhantomData<T>);

What you want here would have to be an opt-in documented promise from containers to not do any of these tricks, and a language guarantee of roughly "two generic instantiations of the same #[repr(Rust)] structure are considered layout-compatible if the only difference is that they contain differently-named but layout-compatible structures." If a structure doesn't promise to uphold that requirement, breaking this property in the future is a non-breaking change.

Hey look, that's me! :smile: Your use case may actually be better served by a closure-based generativity than the macro-based, to be completely honest.

1 Like