Is casting `&Dst` to `&Newtype(Dst)` actually sound?

std contains several custom DSTs like Path, OsStr... that internally cast references like this:

unsafe {
    &*(s.as_ref() as *const OsStr as *const Path)
}

The unsafe guidelines say:

A struct with only one field has the same layout as that field.

However this doesn't mean a reference to a struct with only one field has the same layout as a reference to that field, especially not DST. Length and pointer could be in theory swapped (even if there doesn't seem to be a reason to do so).

Also just because std does it doesn't mean outside code can do it: std is tied to compiler version so if compiler changes to exploit UB std can be changed with it to update the code.

I was unable to find any authoritative answer to this. Does anyone know?

It is if the wrapper type is #[repr(transparent)].

3 Likes

However this doesn't mean a reference to a struct with only one field has the same layout as that field,

Note that the code you quoted is not transmuting a reference (or pointer); it is performing an as cast on the pointer itself. as is not a reinterpretation of the bytes of a value (except, arguably, for integer signedness casts and truncation), but does various things depending on the type of the value, so if the layout of the pointers were different, as would reasonably be expected to accommodate that.

The reference has a note on *T to *V casts saying:

…or T and V are compatible unsized types, e.g., both slices, both the same trait object.

It's not formally stated, but presumably this situation counts as “both slices”.

... I apparently still just need to add an example and then it's good to r+. Whoops

3 Likes

There's no documentation about what happens to references to #[repr(transparent)] types.

Via RFC 401 ("unsize_kind").

(integer casts are not reinterpretations either, they are converting between different sets of equivalence classes)

Why would a reference to a struct with a field ever have the same layout as that field? I am very confused. Did you mean "as a reference to that field"?

Did you mean "as a reference to that field"?

Yes, that's exactly what I meant, edited. Thank you!

So with this clarification, yeah, I do agree that we currently don't allow transmuting between fat references. as isn't a transmute, though, and is generally understood to be guaranteed (and will be if my reference PR lands).

That PR looks great! However I can't find any documentation that says the metadata of DST struct is the same as the metadata of the DST field inside that struct. If they weren't the code would still be UB.

If they aren't, the code won't compile. I don't have wording to point to but this is unequivocally true.

bstr::BStr uses #[repr(transparent)], and interestingly serde_json::value::RawValue uses #[repr(C)]. Should we fix the latter?