I’m not even talking about invariants right now, I don’t even see how “a custom DST that packs some metadata into the ‘data pointer’ portion of references to itself” could even work in the first place. I guess the idea is to rely on those references being produced and consumed by the impl DynamicallySized for TheDST (and thereby special-casing &T-references over all other kinds of pointers)?
But then what about consumers that need to get the address of the size_of_val()-sized, align_of_val()-aligned chunk of memory that is the DST value? To give just one example, Box::drop needs that address (along with size and align) to deallocate memory, and currently it gets it by extracting the “pointer” portion (discarding the metadata).
There’s a way out of this problem: making this conversion from “a &DST what means whatever DST wants it to mean” to “the actual address of the referent” another method of the trait, but at that point it stops being just about dynamic size and starts being closer to overloading the reference type entirely (e.g. tagged pointers that pretend to be regular references are just as attractive for some sized types).
BTW, about this:
This is a quite fragile property. For example, once we decide what to do about padding bytes by e.g. allowing &MaybeUninit<u8> to point to padding bytes (and that this is not only valid but also upholds the safety invariant, in @RalfJung’s terminology), the only reason one can’t cast every &T where T: !Sized to a &[MaybeUninit<u8>; N] is that one doesn’t know a priori what the right N is. But one can guess and check at runtime, so for every N there’s at least a sound &T -> &Option<[MaybeUninit<u8>; N]> conversion which returns Some whenever size_of_val >= N. Admittedly there’s not a lot one can do with that (because who knows which of those bytes are initialized) but I hope it illustrates that this is quite subtle.