Transmuting `Box`es of `#[repr(transparent)]` slice-like DSTs

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");
}
1 Like

Thanks. Your implementation of wrap is definitely much more straightforward.

As for TransparentWrapper, bytemuck provides a derive macro for safely implementing it, so it's pretty convenient.