For transparent struct, the transmute operation is commonly used, and safe enough.
But transmute is marked as unsafe, so unsafe block must be used. It would be much more pleasant if it could support the following syntax (or similar).
#[repr(transparent)]
pub struct I32(i32);
fn foo() {
// use std::mem::transmute;
let i = I32(123);
let _ = i as i32; // same as: let _: i32 = unsafe { transmute(i) };
let i = &mut I32(123);
let _ = i as &mut i32; // same as: let _: &mut i32 = unsafe { transmute(i) };
use std::pin::Pin;
let i = &mut I32(123);
let i = Pin::new(i);
let _ = i as Pin<&mut i32>;// same as: let _: Pin<&mut i32> = unsafe { transmute(i) };
}
I am strongly against expanding as casts to something new like this. As to the transmute, it is important to keep in mind that newtypes can impose safety invariants on the inner type. For this reason a safe transmute that is based solely on its repr would not be sound.
Besides #[repr(transparent)] you would need some other annotation to assert that this transmute is always safe, something like #[all_values_are_valid] or something. This annotation would interact with unsafe code but I think it wouldn't be unsafe per se
Is there a reason not to make the wrapped field pub? That seems closest to âsafe transmute for transparent structâ if you donât need generics (which as wouldnât do either). It clearly demonstrates that thereâs no safety invariant and that itâs okay to access the field directly. It doesnât quite cover your Pin use case, but the others would be fine.
In general, allowing to cast &mut Wrapper<T> to &mut T is unsound, since Wrapper<T> can impose extra invariants. For example,
#[repr(transparent)]
struct NotNull(u32);
let mut not_null = NotNull(1);
{
let inner: &mut u32 = &mut not_null as &mut u32;
*inner = 0;
}
assert_eq!(not_null.0, 0); // BOOM
For similar reasons, we shouldn't generally allow casting Pin<&mut Wrapper<T>> to Pin<&mut T>. Casting a &Wrapper<T> to &T is probably sound, but only if T doesn't have interior mutability, otherwise we have the same issue.
That said, currently it is impossible to safely implement a conversion between &mut T and &mut Wrapper<T>, even if Wrapper<T> is a trivial wrapper without any extra semantics, e.g. if it's used just to sidestep the orphan rules. For this reason some sort of similar feature would imho be desirable. It just should be exposed as a way to safely implement the relevant conversions for the wrapper's author, rather than a free-for-all conversion on #[repr(transparent)] types.
These sorts of conversions shouldn't exactly be based on repr(transparent), that provides more guarantees than required (namely that converting fn(T) -> fn(U) and vice-versa is valid if U is a transparent wrapper of T). Transmuting the value simply requires that they have compatible layouts.
Do these any of these structs have compatible layouts given that randomizing member order is allowed?
struct A { x: f64, y: f64 }
struct B { y: f64, x: f64 }
struct C { a: f64, b: f64 }
Same question if they all have #[repr(C)] on them. Basically, do names matter? Do the declaration order of the names matter?
In a very loose sense, they are compatible, but the semantics may still be very different (imagine if A is Euclidian and B is Polar).
Given that, what kind of syntax would exist to say that A and B are compatible? If they're in different crates, which is allowed to make the declaration (presumably the one that declares one and can "see" all others)? Is this property transitive where if A and B are declared compatible and A and C are compatible that B and C are now compatible?
This doesn't matter for validity, just for safety and avoiding logical bugs (and has semver implications). Which is part of why project-safe-transmute is so complicated, it's not just about whether the transmute is UB or not.
In fact, repr(transparent) both gives more guarantees and less guarantees than needed, as evidenced by NonNull, it guarantees that for the subset of shared valid values the ABI is the same (which requires that for those values the layout is also the same), but says nothing about the cases where rustc_scalar_valid_value is used to restrict validity of the wrapper.