The #[repr(trasparent)]
attribute marks a type to have the same type layout as its single non-zero sized field, but does the same apply to references/pointers of said types when the types are unsized?
Specifically, can I safely cast between &MySlice<T>
and &[T]
in the below example?
#[repr(transparent)]
struct MySlice<T>([T]);
let std_slice: &[i32] = &[ 1, 2, 3 ];
// is this safe?
let my_slice: &MySlice<i32> = unsafe {
std::mem::transmute(std_slice)
};
According to The Rust Reference, the single field of the struct
with the #[repr(transparent)]
will have both the same representation and ABI.
This means that the ABI of a &MySlice<T>
is the same as that of a &[T]
, since MySlice
has no other fields, and we can therefore treat it the same as [T]
. It is therefore safe to convert between them.
However the way you are doing it is unsound, and can actually lead to lifetime errors, since std::mem::transmute
does not keep lifetimes if its type parameters have lifetimes, and neither does pointer casting, although, pointer casting is generally understood to be safer since the compiler can intervene should you make a mistake.
Therefore, the correct way to create a &MySlice
is as follows:
#[repr(transparent)]
struct MySlice<T>([T]);
impl<T> MySlice<T> {
pub fn make<'a>(value: &'a [T]) -> &'a Self {
unsafe {
&*(value as *const [T] as *const Self)
}
}
}
Playground.
This preserves lifetimes and avoids transmute
.
Additionally, one could also use union
s:
#[repr(transparent)]
struct MySlice<T>([T]);
union SliceRepr<'a, T> {
slice: &'a [T],
my_slice: &'a MySlice<T>,
}
impl<T> MySlice<T> {
pub fn make<'a>(value: &'a [T]) -> &'a Self {
unsafe {
SliceRepr { slice: value }.my_slice
}
}
}
Since this also cares for lifetimes.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With