Assumming that bytemuck::TransparentWrapper<Self> is a #[repr(transparent)] wrapper of Self, is this function sound?
use std::mem;
#[repr(C)]
pub struct DstArray<H, D> {
pub header: H,
pub data: [D],
}
impl<H, D> DstArray<H, D> {
pub fn wrap<W>(self: Box<Self>) -> Box<W>
where
W: bytemuck::TransparentWrapper<Self> +?Sized,
{
let self_box = mem::MaybeUninit::new(self);
// - `W` is transparent wrapper of `Self`
// - Ptr metadata of `&Self` is length, so the metadata of `&W` must be the same length.
//
// Does this imply that we can transmute
// `MaybeUninit<Box<Self>>` to `MaybeUninit<Box<W>>`?
let w_box: mem::MaybeUninit<Box<W>> = unsafe { mem::transmute_copy(&self_box) };
unsafe { w_box.assume_init() }
}
}
TransparentWrapper is a trait, so the question itself doesn't make much sense. You probably want to be asking "assuming that W is a valid TransparentWrapper<Self>, is it sound?"
The answer is: it depends. TransparentWrapper is an inherently unsafe trait and is more of a "marker" than anything else, to be used in place of a generic #[repr(transparent)]. Assuming the W type does indeed match the Self as per the layout: yes, it is going to be sound.
You can create your own ad-hoc version, even: it will have the exact same purpose and all the same safety requirements with all the ways to shoot yourself in the foot:
Summary
use std::mem::ManuallyDrop;
#[repr(C)]
pub struct DstArray<H, D> {
pub header: H,
pub data: [D],
}
#[repr(C)]
struct HeadArray<T: ?Sized> {
header: u64,
data: T
}
pub unsafe trait SameLayoutAs<T: ?Sized> {
fn wrap(self: Box<Self>) -> Box<T> {
// prevents the box from being dropped
// + freed/deallocated at the end of the scope
let no_drop = ManuallyDrop::new(self);
// pointers to box are always thin (no metadata needed),
// casting one in between the other is as simple as:
let box_ptr = &*no_drop as *const Box<Self> as *const Box<T>;
// SAFETY: copies the `Box<Self>` as `Box<T>` back to the stack
// without dropping the original (thanks to the `ManuallyDrop`);
// as long as the layout of `Self` and `T` are the same
// (as guaranteed by the `unsafe impl` itself)
// no memory safety issues can occur
unsafe { std::ptr::read(box_ptr) }
}
}
unsafe impl SameLayoutAs<DstArray<u64, u8>> for HeadArray<[u8]> {}
unsafe impl SameLayoutAs<HeadArray<[u8]>> for DstArray<u64, u8> {}
#[test]
fn dst_array_test() {
let head_arr = Box::new(HeadArray {
header: 0xBEEF, // u64
data: *b"not 0xDEAD" // [u8; 10]
});
let coerced = unsafe {
let raw = Box::into_raw(head_arr);
// unsized coercion, see [https://doc.rust-lang.org/nomicon/exotic-sizes.html#dynamically-sized-types-dsts]
let raw_dst: *mut HeadArray<[u8]> = raw;
Box::from_raw(raw_dst)
};
let dst = coerced.wrap();
assert_eq!(dst.header, 0xBEEF);
assert_eq!(&dst.data, b"not 0xDEAD");
let head = dst.wrap();
assert_eq!(head.header, 0xBEEF);
assert_eq!(&head.data, b"not 0xDEAD");
}