simple-dst 0.2.0

Traits for allocating and using custom DSTs.
Documentation
use core::mem::offset_of;

use alloc::borrow::ToOwned;

use crate::*;

#[cfg(feature = "alloc")]
#[test]
fn str_test() {
    let str = "thisisatest";
    let layout = Layout::for_value(str);
    // SAFETY: because the layout and len arguments are correct, the pointer given to
    // the init closure is valid for writing the contents of str to.
    let boxed: Box<str> = unsafe {
        Box::new_dst(str.len(), layout, |ptr| {
            str.clone_to_uninit(ptr.cast().as_ptr());
        })
    };

    assert_eq!(boxed.len(), str.len());
    assert_eq!(boxed.as_ref(), str);
}

#[cfg(feature = "alloc")]
#[test]
fn zst_test() {
    let arr: [(); 0] = [];
    let layout = Layout::for_value(&arr);
    // SAFETY: because the layout and len arguments are correct, the pointer given to
    // the init closure is valid for writing the contents of arr to.
    let boxed: Box<[()]> = unsafe {
        Box::new_dst(arr.len(), layout, |ptr| {
            arr.clone_to_uninit(ptr.cast().as_ptr());
        })
    };

    assert_eq!(boxed.len(), arr.len());
    assert_eq!(boxed.as_ref(), arr);
}

#[repr(C)]
struct Type {
    data1: i16,
    data2: usize,
    data3: u32,
    slice: [i128],
}

// SAFETY: implemented correctly.
unsafe impl Dst for Type {
    #[inline]
    fn len(&self) -> usize {
        self.slice.len()
    }

    fn layout(len: usize) -> Result<Layout, LayoutError> {
        let (layout, _) = Self::__dst_impl_layout_offsets(len)?;
        Ok(layout)
    }

    fn retype(ptr: NonNull<u8>, len: usize) -> NonNull<Self> {
        // FUTURE: switch to ptr::from_raw_parts_mut() when it has stabilised.
        // SAFETY: the pointer value doesn't change when using `slice_from_raw_parts_mut`,
        // so the invariants of `NonNull` are upheld
        unsafe {
            #[allow(
                clippy::cast_ptr_alignment,
                reason = "the responsibility to provide a pointer with the correct alignment is on the caller"
            )]
            NonNull::new_unchecked(ptr::slice_from_raw_parts_mut(ptr.as_ptr(), len) as *mut Self)
        }
    }
}

impl Type {
    fn __dst_impl_layout_offsets(len: usize) -> Result<(Layout, [usize; 4]), LayoutError> {
        let layouts = [
            Layout::new::<i16>(),
            Layout::new::<usize>(),
            Layout::new::<u32>(),
            <[i128]>::layout(len)?,
        ];
        let mut offsets = [0; 4];
        let layout = Layout::from_size_align(0, 1)?;
        let (layout, offset) = layout.extend(layouts[0])?;
        offsets[0] = offset;
        let (layout, offset) = layout.extend(layouts[1])?;
        offsets[1] = offset;
        let (layout, offset) = layout.extend(layouts[2])?;
        offsets[2] = offset;
        let (layout, offset) = layout.extend(layouts[3])?;
        offsets[3] = offset;
        Ok((layout.pad_to_align(), offsets))
    }

    unsafe fn new_unchecked<A: AllocDst<Self>>(
        data1: i16,
        data2: usize,
        data3: u32,
        slice: &[i128],
    ) -> Result<A, LayoutError> {
        let (layout, offsets) = Self::__dst_impl_layout_offsets(slice.len())?;
        // SAFETY: the offsets from above are valid offsets for writing to, and the
        // writes are done only after the clone_to_uninit call is done, meaning that
        // there is no chance of panicking, and thus leaking memory.
        Ok(unsafe {
            A::new_dst(slice.len(), layout, |ptr| {
                let dest = ptr.cast::<u8>();
                slice.clone_to_uninit(dest.add(offsets[3]).as_ptr());
                dest.add(offsets[0]).cast().write(data1);
                dest.add(offsets[1]).cast().write(data2);
                dest.add(offsets[2]).cast().write(data3);
            })
        })
    }
}

// SAFETY: implemented correctly.
unsafe impl CloneToUninit for Type {
    #[allow(
        clippy::clone_on_copy,
        reason = "matching the output of the derive macro"
    )]
    #[allow(
        clippy::cast_ptr_alignment,
        reason = "the responsibility to provide a pointer with the correct alignment is on the caller"
    )]
    unsafe fn clone_to_uninit(&self, dest: *mut u8) {
        // SAFETY:
        // * `&self.slice` >= `self` because `slice` is a field in `self`, and Self is
        //   `#[repr(C)]`.
        // * both pointers must be from the same allocation since they are within the
        //   same object, and thus the memory range between them is also in bounds of
        //   the object.
        // * the distance between the pointers is an exact multiple of the size of u8.
        let last_offset = unsafe { (&raw const self.slice).byte_offset_from_unsigned(self) };

        let data1 = self.data1.clone();
        let data2 = self.data2.clone();
        let data3 = self.data3.clone();

        // SAFETY: the offset from above and the offsets below are valid offsets for
        // writing to, and the writes are done only after the all the clones and the
        // clone_to_uninit call is done, meaning that there is no chance of panicking,
        // and thus leaking memory.
        unsafe {
            self.slice.clone_to_uninit(dest.add(last_offset));
            dest.add(offset_of!(Self, data1)).cast::<i16>().write(data1);
            dest.add(offset_of!(Self, data2))
                .cast::<usize>()
                .write(data2);
            dest.add(offset_of!(Self, data3)).cast::<u32>().write(data3);
        }
    }
}

impl ToOwned for Type {
    type Owned = Box<Type>;

    fn to_owned(&self) -> Self::Owned {
        let layout = Layout::for_value(self);

        // SAFETY: because the layout and len arguments are correct, the pointer given
        // to the init closure is valid for writing the cloned data to.
        unsafe {
            Self::Owned::new_dst(self.len(), layout, |ptr| {
                let dest = ptr.cast::<u8>();

                self.clone_to_uninit(dest.as_ptr());
            })
        }
    }
}

#[cfg(feature = "alloc")]
#[test]
fn complex_test() {
    // SAFETY: `Type` has no interior invariants.
    let v: Box<_> = unsafe { Type::new_unchecked(-12, 65537, 50, &[-2, 5, 20]) }.unwrap();
    assert_eq!(v.data1, -12);
    assert_eq!(v.data2, 65537);
    assert_eq!(v.data3, 50);
    assert_eq!(v.slice.first(), Some(&-2));
    assert_eq!(v.slice.get(1), Some(&5));
    assert_eq!(v.slice.get(2), Some(&20));
    assert_eq!(v.len(), 3);
    assert_eq!(v.len(), v.slice.len());
}

#[cfg(feature = "alloc")]
#[test]
fn clone_test() {
    // SAFETY: `Type` has no interior invariants.
    let v1: Box<_> = unsafe { Type::new_unchecked(-12, 65537, 50, &[-2, 5, 20]) }.unwrap();

    let v2 = v1.to_owned();
    assert_eq!(v2.data1, v1.data1);
    assert_eq!(v2.data2, v1.data2);
    assert_eq!(v2.data3, v1.data3);
    assert_eq!(v2.slice.first(), v1.slice.first());
    assert_eq!(v2.slice.get(1), v1.slice.get(1));
    assert_eq!(v2.slice.get(2), v1.slice.get(2));
    assert_eq!(v2.len(), v1.len());
}

#[cfg(feature = "alloc")]
#[derive(Dst, CloneToUninit, ToOwned)]
#[dst(simple_dst_path = crate)]
#[to_owned(alloc_path = alloc)]
#[repr(C)]
struct DeriveType {
    data1: i16,
    data2: usize,
    data3: u32,
    slice: [i128],
}

#[cfg(feature = "alloc")]
#[test]
fn derive_complex_test() {
    // SAFETY: `Type` has no interior invariants.
    let v: Box<_> = unsafe { DeriveType::new_unchecked(-12, 65537, 50, &[-2, 5, 20]) }.unwrap();
    assert_eq!(v.data1, -12);
    assert_eq!(v.data2, 65537);
    assert_eq!(v.data3, 50);
    assert_eq!(v.slice.first(), Some(&-2));
    assert_eq!(v.slice.get(1), Some(&5));
    assert_eq!(v.slice.get(2), Some(&20));
    assert_eq!(v.len(), 3);
    assert_eq!(v.len(), v.slice.len());
}

#[cfg(feature = "alloc")]
#[test]
fn derive_clone_test() {
    // SAFETY: `Type` has no interior invariants.
    let v1: Box<_> = unsafe { DeriveType::new_unchecked(-12, 65537, 50, &[-2, 5, 20]) }.unwrap();

    let v2 = v1.to_owned();
    assert_eq!(v2.data1, v1.data1);
    assert_eq!(v2.data2, v1.data2);
    assert_eq!(v2.data3, v1.data3);
    assert_eq!(v2.slice.first(), v1.slice.first());
    assert_eq!(v2.slice.get(1), v1.slice.get(1));
    assert_eq!(v2.slice.get(2), v1.slice.get(2));
    assert_eq!(v2.len(), v1.len());
}