What to watch out for using repr(transparent) to add type data

#[repr(transparent)]
struct Wrapper<T: ?Sized, U> {
    inner: T,
    marker: PhantomData<U>,
}

fn add_marker<T: ?Sized, U>(inner: &mut T) -> &mut Wrapper<T, U> {
    let ptr = (inner as *mut T).cast();
    unsafe { &mut *ptr }
}

I'm considering using the above API to allow adding type-level data onto a mutable reference, but I want to know, is it fully sound? There shouldn't be any room to violate validity requirements like Zero to NonZero (since the type parameter T is passed through unchanged) but I'm not sure if there's anything else that needs to be watched out for.

I would be implementing (all safe) methods on Wrapper that I would not be able to on the bare T as they depend on the U type parameter.

Yes, this should be sound. In fact, the compiler would reject the repr(transparent) in case the second field would be problematic (i. e. in case there's more than one field of non-zero size).

On an unrelated note, consider using PhantomData<fn() -> U> to avoid inheriting unnecessary auto-trait bounds from the U type; and if you don't want the Uparameter to be covariant, you could also consider fn(U) -> U or fn() -> Cell<U> or something like that in the PhantomData.

2 Likes

Does repr(transparent) guarantee in this case that the pointer metadata for &mut Wrapper<dyn Trait, U> is the same as the metadata for &mut dyn Trait?

I tried it in miri (minor tweaks to make it compile) which passes, and it would make sense for this property to be true, but I couldn't find it specifically documented anywhere.

The documentation isn't great on this point, but references to custom DSTs have the same metadata as references to their single DST field (and thus ultimately, in your case, the vtable for dyn Trait).

1 Like