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! Your use case may actually be better served by a closure-based generativity than the macro-based, to be completely honest.