thin-dst 1.1.0

Thin pointers to inline-slice dynamically sized types
Documentation
//! Boxed custom DSTs that store a slice and the length of said slice inline.
//! Uses the standard library collection types for full interoperability,
//! and also provides thin owned pointers for space-conscious use.
//!
//! # Examples
//!
//! The simplest example is just a boxed slice:
//!
//! ```rust
//! # use thin_dst::*;
//! let boxed_slice = ThinBox::new((), vec![0, 1, 2, 3, 4, 5]);
//! assert_eq!(&*boxed_slice, &[0, 1, 2, 3, 4, 5][..]);
//! let boxed_slice: Box<ThinData<(), u32>> = boxed_slice.into();
//! ```
//!
//! All of the thin collection types are constructed with a "head" and a "tail".
//! The head is any `Sized` type that you would like to associate with the slice.
//! The "tail" is the owned slice of data that you would like to store.
//!
//! This creates a collection of `ThinData`, which acts like `{ head, tail }`,
//! and also handles the `unsafe` required for both custom slice DSTs and thin DST pointers.
//! The most idiomatic usage is to encapsulate the use of thin-dst with a transparent newtype:
//!
//! ```rust
//! # use thin_dst::*; struct NodeHead;
//! #[repr(transparent)]
//! struct NodeData(ThinData<NodeHead, Node>);
//! struct Node(ThinArc<NodeHead, Node>);
//! ```
//!
//! And then use `NodeData` by transmuting and/or [ref-cast]ing as needed.
//!
//!   [ref-cast]: <https://lib.rs/crates/ref-cast>

#![no_std]
extern crate alloc;

use {
    crate::polyfill::*,
    alloc::{
        alloc::{alloc, dealloc, handle_alloc_error, Layout, LayoutErr},
        boxed::Box,
        rc::Rc,
        sync::Arc,
    },
    core::{
        cmp::{self, PartialEq},
        fmt::{self, Debug},
        hash,
        marker::PhantomData,
        mem::ManuallyDrop,
        ops::{Deref, DerefMut},
        ptr::{self, NonNull},
    },
};

mod polyfill;

/// An erased pointer with size and stride of one byte.
pub type ErasedPtr = NonNull<priv_in_pub::Erased>;
#[doc(hidden)]
pub mod priv_in_pub {
    // This MUST be size=1 such that pointer math actually advances the pointer.
    // FUTURE(extern_types): expose as `extern type` (breaking)
    // This will require casting to NonNull<u8> everywhere for pointer offsetting.
    // But that's not a bad thing. It would have saved a good deal of headache.
    pub struct Erased {
        #[allow(unused)]
        raw: u8,
    }
}

/// A custom slice-holding dynamically sized type.
/// Stores slice length inline to be thin-pointer compatible.
///
/// # Stability
///
/// Note that even though this struct is `#[repr(C)]`,
/// the offsets of its public fields are _not public_.
/// A private field appears before them,
/// so their offset should be treated as being unknown.
#[repr(C)]
#[derive(Debug, Eq, PartialEq, Hash)]
pub struct ThinData<Head, SliceItem> {
    // NB: Optimal layout packing is
    //     align(usize) < align(head) => head before len
    //     align(head) < align(usize) => len before head
    // We put len first because
    //     a) it's much simpler to go from ErasedPtr to ptr-to-length
    //     b) it's rare for types to have align > align(usize)
    // For optimality, we should use repr(Rust) and pointer projection or offset_of!
    // We don't do that for now since we can avoid the unsoundness of offset_of!,
    // and offset_of! doesn't work for ?Sized types anyway.
    // SAFETY: must be length of self.slice
    len: usize,
    /// The sized portion of this DST.
    pub head: Head,
    /// The slice portion of this DST.
    pub slice: [SliceItem],
}

impl<Head, SliceItem> ThinData<Head, SliceItem> {
    fn len(ptr: ErasedPtr) -> NonNull<usize> {
        ptr.cast()
    }

    fn erase(ptr: NonNull<Self>) -> ErasedPtr {
        ptr.cast()
    }

    unsafe fn fatten_const(ptr: ErasedPtr) -> NonNull<Self> {
        let len = ptr::read(Self::len(ptr).as_ptr());
        let slice = make_slice(ptr.cast::<SliceItem>().as_ptr(), len);
        NonNull::new_unchecked(slice as *const Self as *mut Self)
    }

    unsafe fn fatten_mut(ptr: ErasedPtr) -> NonNull<Self> {
        let len = ptr::read(Self::len(ptr).as_ptr());
        let slice = make_slice_mut(ptr.cast::<SliceItem>().as_ptr(), len);
        NonNull::new_unchecked(slice as *mut Self)
    }
}

impl<SliceItem: PartialEq> PartialEq<[SliceItem]> for ThinData<(), SliceItem> {
    fn eq(&self, other: &[SliceItem]) -> bool {
        &self.slice == other
    }
}

macro_rules! thin_holder {
    ( #[nodrop] for $thin:ident<$($a:lifetime,)* Head, SliceItem> as $fat:ident<$($b:lifetime,)* ThinData<Head, SliceItem>> with $fatten:ident ) => {
        impl<$($a,)* Head, SliceItem> $thin<$($a,)* Head, SliceItem> {
            /// Construct an owned pointer from an erased pointer.
            ///
            /// # Safety
            ///
            /// This pointer must logically own a valid instance of `Self`.
            pub unsafe fn from_erased(ptr: ErasedPtr) -> Self {
                Self {
                    raw: ptr,
                    marker: PhantomData,
                }
            }

            /// Convert this owned pointer into an erased pointer.
            ///
            /// To avoid a memory leak the pointer must be converted back
            /// using `Self::from_erased`.
            pub fn erase(this: Self) -> ErasedPtr {
                let this = ManuallyDrop::new(this);
                this.raw
            }
        }

        impl<$($a,)* Head, SliceItem> From<$fat<$($b,)* ThinData<Head, SliceItem>>> for $thin<$($a,)* Head, SliceItem> {
            fn from(this: $fat<$($b,)* ThinData<Head, SliceItem>>) -> $thin<$($a,)* Head, SliceItem> {
                unsafe {
                    let this = NonNull::new_unchecked($fat::into_raw(this) as *mut _);
                    Self::from_erased(ThinData::<Head, SliceItem>::erase(this))
                }
            }
        }

        impl<$($a,)* Head, SliceItem> Deref for $thin<$($a,)* Head, SliceItem>
        where
            $fat<$($b,)* ThinData<Head, SliceItem>>: Deref,
        {
            type Target = ThinData<Head, SliceItem>;
            fn deref(&self) -> &ThinData<Head, SliceItem> {
                unsafe { &*ThinData::fatten_const(self.raw).as_ptr() }
            }
        }

        impl<$($a,)* Head, SliceItem> DerefMut for $thin<$($a,)* Head, SliceItem>
        where
            $fat<$($b,)* ThinData<Head, SliceItem>>: DerefMut,
        {
            fn deref_mut(&mut self) -> &mut ThinData<Head, SliceItem> {
                unsafe { &mut *ThinData::fatten_mut(self.raw).as_ptr() }
            }
        }

        impl<$($a,)* Head, SliceItem> Debug for $thin<$($a,)* Head, SliceItem>
        where
            $fat<$($b,)* ThinData<Head, SliceItem>>: Debug,
        {
            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
                unsafe {
                    let this = ManuallyDrop::new($fat::from_raw(ThinData::fatten_const(self.raw).as_ptr()));
                    this.fmt(f)
                }
            }
        }

        unsafe impl<$($a,)* Head, SliceItem> Send for $thin<$($a,)* Head, SliceItem> where
            $fat<$($b,)* ThinData<Head, SliceItem>>: Send
        {
        }
        unsafe impl<$($a,)* Head, SliceItem> Sync for $thin<$($a,)* Head, SliceItem> where
            $fat<$($b,)* ThinData<Head, SliceItem>>: Sync
        {
        }

        impl<$($a,)* Head, SliceItem> cmp::Eq for $thin<$($a,)* Head, SliceItem> where
            $fat<$($b,)* ThinData<Head, SliceItem>>: cmp::Eq,
        {
        }
        impl<$($a,)* Head, SliceItem> PartialEq for $thin<$($a,)* Head, SliceItem>
        where
            $fat<$($b,)* ThinData<Head, SliceItem>>: PartialEq,
        {
            fn eq(&self, other: &Self) -> bool {
                unsafe {
                    let other = ManuallyDrop::new($fat::from_raw(ThinData::fatten_const(other.raw).as_ptr()));
                    <Self as PartialEq<$fat<$($b,)* ThinData<Head, SliceItem>>>>::eq(self, &other)
                }
            }
        }
        impl<$($a,)* Head, SliceItem> PartialEq<$fat<$($b,)* ThinData<Head, SliceItem>>> for $thin<$($a,)* Head, SliceItem>
        where
            $fat<$($b,)* ThinData<Head, SliceItem>>: PartialEq,
        {
            fn eq(&self, other: &$fat<$($b,)* ThinData<Head, SliceItem>>) -> bool {
                unsafe {
                    let this = ManuallyDrop::new($fat::from_raw(ThinData::fatten_const(self.raw).as_ptr()));
                    <$fat<$($b,)* ThinData<Head, SliceItem>> as PartialEq>::eq(&this, other)
                }
            }
        }

        impl<$($a,)* Head, SliceItem> hash::Hash for $thin<$($a,)* Head, SliceItem>
        where
            $fat<$($b,)* ThinData<Head, SliceItem>>: hash::Hash,
        {
            fn hash<H>(&self, state: &mut H)
            where
                H: hash::Hasher,
            {
                unsafe {
                    let this = ManuallyDrop::new($fat::from_raw(ThinData::fatten_const(self.raw).as_ptr()));
                    <$fat<$($b,)* ThinData<Head, SliceItem>> as hash::Hash>::hash(&this, state)
                }
            }
        }
    };
    ( for $thin:ident<$($a:lifetime,)* Head, SliceItem> as $fat:ident<$($b:lifetime,)* ThinData<Head, SliceItem>> with $fatten:ident ) => {
        impl<$($a,)* Head, SliceItem> Drop for $thin<$($a,)* Head, SliceItem> {
            fn drop(&mut self) {
                let this = unsafe { $fat::from_raw(ThinData::$fatten(self.raw).as_ptr()) };
                drop::<$fat<$($b,)* ThinData<Head, SliceItem>>>(this)
            }
        }

        thin_holder!(#[nodrop] for $thin<$($a,)* Head, SliceItem> as $fat<$($b,)* ThinData<Head, SliceItem>> with $fatten );
    };
}

/// A thin version of [`Box`].
///
///   [`Box`]: <https://doc.rust-lang.org/stable/std/boxed/struct.Box.html>
pub struct ThinBox<Head, SliceItem> {
    raw: ErasedPtr,
    marker: PhantomData<Box<ThinData<Head, SliceItem>>>,
}

thin_holder!(for ThinBox<Head, SliceItem> as Box<ThinData<Head, SliceItem>> with fatten_mut);

impl<Head, SliceItem> ThinBox<Head, SliceItem> {
    fn layout(len: usize) -> Result<(Layout, [usize; 3]), LayoutErr> {
        let length_layout = Layout::new::<usize>();
        let head_layout = Layout::new::<Head>();
        let slice_layout = layout_array::<SliceItem>(len)?;
        repr_c_3([length_layout, head_layout, slice_layout])
    }

    unsafe fn alloc(len: usize, layout: Layout) -> NonNull<ThinData<Head, SliceItem>> {
        let ptr: ErasedPtr = NonNull::new(alloc(layout))
            .unwrap_or_else(|| handle_alloc_error(layout))
            .cast();
        ptr::write(ThinData::<Head, SliceItem>::len(ptr).as_ptr(), len);
        ThinData::fatten_mut(ptr.cast())
    }

    /// Create a new boxed `ThinData` with the given head and slice.
    ///
    /// # Panics
    ///
    /// Panics if the slice iterator incorrectly reports its length.
    pub fn new<I>(head: Head, slice: I) -> Self
    where
        I: IntoIterator<Item = SliceItem>,
        I::IntoIter: ExactSizeIterator, // + TrustedLen
    {
        struct InProgress<Head, SliceItem> {
            raw: NonNull<ThinData<Head, SliceItem>>,
            written_len: usize,
            layout: Layout,
            head_offset: usize,
            slice_offset: usize,
        }

        impl<Head, SliceItem> Drop for InProgress<Head, SliceItem> {
            fn drop(&mut self) {
                let raw_ptr = ThinData::erase(self.raw).as_ptr();
                unsafe {
                    let slice = make_slice_mut(
                        raw_ptr.add(self.slice_offset).cast::<SliceItem>(),
                        self.written_len,
                    );
                    ptr::drop_in_place(slice);
                    dealloc(raw_ptr.cast(), self.layout);
                }
            }
        }

        impl<Head, SliceItem> InProgress<Head, SliceItem> {
            fn raw_ptr(&self) -> ErasedPtr {
                ThinData::erase(self.raw)
            }

            fn new(len: usize) -> Self {
                let (layout, [_, head_offset, slice_offset]) =
                    ThinBox::<Head, SliceItem>::layout(len)
                        .unwrap_or_else(|e| panic!("oversize box: {}", e));
                InProgress {
                    raw: unsafe { ThinBox::alloc(len, layout) },
                    written_len: 0,
                    layout,
                    head_offset,
                    slice_offset,
                }
            }

            unsafe fn push(&mut self, item: SliceItem) {
                self.raw_ptr()
                    .as_ptr()
                    .add(self.slice_offset)
                    .cast::<SliceItem>()
                    .add(self.written_len)
                    .write(item);
                self.written_len += 1;
            }

            unsafe fn finish(self, head: Head) -> ThinBox<Head, SliceItem> {
                let this = ManuallyDrop::new(self);
                let ptr = this.raw_ptr();
                ptr::write(ptr.as_ptr().add(this.head_offset).cast(), head);
                let out = ThinBox::from_erased(ptr);
                assert_eq!(this.layout, Layout::for_value(&*out));
                out
            }
        }

        let mut items = slice.into_iter();
        let len = items.len();

        unsafe {
            let mut this = InProgress::new(len);

            for _ in 0..len {
                let slice_item = items
                    .next()
                    .expect("ExactSizeIterator over-reported length");
                this.push(slice_item);
            }
            assert!(
                items.next().is_none(),
                "ExactSizeIterator under-reported length"
            );

            this.finish(head)
        }
    }
}

impl<Head, SliceItem> From<ThinBox<Head, SliceItem>> for Box<ThinData<Head, SliceItem>> {
    fn from(this: ThinBox<Head, SliceItem>) -> Self {
        unsafe {
            let this = ManuallyDrop::new(this);
            Box::from_raw(ThinData::fatten_mut(this.raw).as_ptr())
        }
    }
}

impl<Head, SliceItem> Clone for ThinBox<Head, SliceItem>
where
    Head: Clone,
    SliceItem: Clone,
{
    // TODO: this should be able to just be
    //     ThinBox::new(self.head.clone(), self.slice.iter().cloned())
    fn clone(&self) -> Self {
        ThinBox::new(self.head.clone(), self.slice.iter().cloned())
    }
}

/// A thin version of [`Arc`].
///
///   [`Arc`]: <https://doc.rust-lang.org/stable/std/sync/struct.Arc.html>
pub struct ThinArc<Head, SliceItem> {
    raw: ErasedPtr,
    marker: PhantomData<Arc<ThinData<Head, SliceItem>>>,
}

thin_holder!(for ThinArc<Head, SliceItem> as Arc<ThinData<Head, SliceItem>> with fatten_const);

impl<Head, SliceItem> ThinArc<Head, SliceItem> {
    /// Create a new atomically reference counted `ThinData` with the given head and slice.
    ///
    /// # Panics
    ///
    /// Panics if the slice iterator incorrectly reports its length.
    ///
    /// # Note on allocation
    ///
    /// This currently creates a `ThinBox` first and then moves that into an `Arc`.
    /// This is required, because the heap layout of `Arc` is not stable,
    /// and custom DSTs need to be manually allocated.
    ///
    /// This will be eliminated in the future if/when the
    /// reference counted heap layout is stabilized.
    pub fn new<I>(head: Head, slice: I) -> Self
    where
        I: IntoIterator<Item = SliceItem>,
        I::IntoIter: ExactSizeIterator, // + TrustedLen
    {
        // FUTURE(https://internals.rust-lang.org/t/stabilizing-a-rc-layout/11265):
        //     When/if `Arc`'s heap repr is stable, allocate directly rather than `Box` first.
        let boxed: Box<ThinData<Head, SliceItem>> = ThinBox::new(head, slice).into();
        let arc: Arc<ThinData<Head, SliceItem>> = boxed.into();
        arc.into()
    }
}

impl<Head, SliceItem> From<ThinArc<Head, SliceItem>> for Arc<ThinData<Head, SliceItem>> {
    fn from(this: ThinArc<Head, SliceItem>) -> Self {
        unsafe {
            let this = ManuallyDrop::new(this);
            Arc::from_raw(ThinData::fatten_const(this.raw).as_ptr())
        }
    }
}

impl<Head, SliceItem> Clone for ThinArc<Head, SliceItem>
where
    Arc<ThinData<Head, SliceItem>>: Clone,
{
    fn clone(&self) -> Self {
        unsafe {
            let this = ManuallyDrop::new(Arc::from_raw(ThinData::fatten_const(self.raw).as_ptr()));
            ManuallyDrop::into_inner(ManuallyDrop::clone(&this)).into()
        }
    }
}

/// A thin version of [`Rc`].
///
///   [`Rc`]: <https://doc.rust-lang.org/stable/std/rc/struct.Rc.html>
pub struct ThinRc<Head, SliceItem> {
    raw: ErasedPtr,
    marker: PhantomData<Rc<ThinData<Head, SliceItem>>>,
}

thin_holder!(for ThinRc<Head, SliceItem> as Rc<ThinData<Head, SliceItem>> with fatten_const);

impl<Head, SliceItem> ThinRc<Head, SliceItem> {
    /// Create a new reference counted `ThinData` with the given head and slice.
    ///
    /// # Panics
    ///
    /// Panics if the slice iterator incorrectly reports its length.
    ///
    /// # Note on allocation
    ///
    /// This currently creates a `ThinBox` first and then moves that into an `Rc`.
    /// This is required, because the heap layout of `Rc` is not stable,
    /// and custom DSTs need to be manually allocated.
    ///
    /// This will be eliminated in the future if/when the
    /// reference counted heap layout is stabilized.
    pub fn new<I>(head: Head, slice: I) -> Self
    where
        I: IntoIterator<Item = SliceItem>,
        I::IntoIter: ExactSizeIterator, // + TrustedLen
    {
        // FUTURE(https://internals.rust-lang.org/t/stabilizing-a-rc-layout/11265):
        //     When/if `Rc`'s heap repr is stable, allocate directly rather than `Box` first.
        let boxed: Box<ThinData<Head, SliceItem>> = ThinBox::new(head, slice).into();
        let arc: Rc<ThinData<Head, SliceItem>> = boxed.into();
        arc.into()
    }
}

impl<Head, SliceItem> From<ThinRc<Head, SliceItem>> for Rc<ThinData<Head, SliceItem>> {
    fn from(this: ThinRc<Head, SliceItem>) -> Self {
        unsafe {
            let this = ManuallyDrop::new(this);
            Rc::from_raw(ThinData::fatten_const(this.raw).as_ptr())
        }
    }
}

impl<Head, SliceItem> Clone for ThinRc<Head, SliceItem>
where
    Rc<ThinData<Head, SliceItem>>: Clone,
{
    fn clone(&self) -> Self {
        unsafe {
            let this = ManuallyDrop::new(Rc::from_raw(ThinData::fatten_const(self.raw).as_ptr()));
            ManuallyDrop::into_inner(ManuallyDrop::clone(&this)).into()
        }
    }
}

pub struct ThinRef<'a, Head, SliceItem> {
    raw: ErasedPtr,
    marker: PhantomData<&'a ThinData<Head, SliceItem>>,
}

thin_holder!(#[nodrop] for ThinRef<'a, Head, SliceItem> as Ref<'a, ThinData<Head, SliceItem>> with fatten_const);

impl<'a, Head, SliceItem> Copy for ThinRef<'a, Head, SliceItem> where
    &'a ThinData<Head, SliceItem>: Copy
{
}
impl<'a, Head, SliceItem> Clone for ThinRef<'a, Head, SliceItem>
where
    &'a ThinData<Head, SliceItem>: Clone,
{
    fn clone(&self) -> Self {
        *self
    }
}

impl<'a, Head, SliceItem> From<ThinRef<'a, Head, SliceItem>> for &'a ThinData<Head, SliceItem> {
    fn from(this: ThinRef<'a, Head, SliceItem>) -> Self {
        unsafe { Ref::from_raw(ThinData::fatten_const(this.raw).as_ptr()) }
    }
}

pub struct ThinRefMut<'a, Head, SliceItem> {
    raw: ErasedPtr,
    marker: PhantomData<&'a mut ThinData<Head, SliceItem>>,
}

thin_holder!(#[nodrop] for ThinRefMut<'a, Head, SliceItem> as Ref<'a, ThinData<Head, SliceItem>> with fatten_const);

impl<'a, Head, SliceItem> From<ThinRefMut<'a, Head, SliceItem>>
    for &'a mut ThinData<Head, SliceItem>
{
    fn from(this: ThinRefMut<'a, Head, SliceItem>) -> Self {
        unsafe { RefMut::from_raw(ThinData::fatten_mut(this.raw).as_ptr()) }
    }
}

pub struct ThinPtr<Head, SliceItem> {
    raw: ErasedPtr,
    marker: PhantomData<NonNull<ThinData<Head, SliceItem>>>,
}

thin_holder!(#[nodrop] for ThinPtr<Head, SliceItem> as NonNull<ThinData<Head, SliceItem>> with fatten_mut);

impl<Head, SliceItem> Copy for ThinPtr<Head, SliceItem> where
    NonNull<ThinData<Head, SliceItem>>: Copy
{
}
impl<Head, SliceItem> Clone for ThinPtr<Head, SliceItem>
where
    NonNull<ThinData<Head, SliceItem>>: Clone,
{
    fn clone(&self) -> Self {
        *self
    }
}

impl<Head, SliceItem> From<ThinPtr<Head, SliceItem>> for NonNull<ThinData<Head, SliceItem>> {
    fn from(this: ThinPtr<Head, SliceItem>) -> Self {
        unsafe { ThinData::fatten_mut(this.raw) }
    }
}

#[allow(
    missing_docs,
    clippy::missing_safety_doc,
    clippy::should_implement_trait
)]
impl<Head, SliceItem> ThinPtr<Head, SliceItem> {
    pub unsafe fn as_ptr(self) -> *mut ThinData<Head, SliceItem> {
        let nn: NonNull<_> = self.into();
        nn.as_ptr()
    }
    pub unsafe fn as_ref(&self) -> &ThinData<Head, SliceItem> {
        &*self.as_ptr()
    }
    pub unsafe fn as_mut(&mut self) -> &mut ThinData<Head, SliceItem> {
        &mut *self.as_ptr()
    }
}

// helpers for implementing ThinRef[Mut] and ThinPtr[Mut]

unsafe trait RawExt<T: ?Sized> {
    unsafe fn from_raw(ptr: *const T) -> Self;
    unsafe fn into_raw(self) -> *const T;
}

unsafe trait RawMutExt<T: ?Sized> {
    unsafe fn from_raw(ptr: *mut T) -> Self;
    unsafe fn into_raw(self) -> *mut T;
}

type Ref<'a, T> = &'a T;
unsafe impl<'a, T: ?Sized> RawExt<T> for Ref<'a, T> {
    unsafe fn from_raw(ptr: *const T) -> Self {
        &*ptr
    }

    unsafe fn into_raw(self) -> *const T {
        self
    }
}

type RefMut<'a, T> = &'a mut T;
unsafe impl<'a, T: ?Sized> RawMutExt<T> for RefMut<'a, T> {
    unsafe fn from_raw(ptr: *mut T) -> Self {
        &mut *ptr
    }

    unsafe fn into_raw(self) -> *mut T {
        self
    }
}

unsafe impl<T: ?Sized> RawMutExt<T> for NonNull<T> {
    unsafe fn from_raw(ptr: *mut T) -> Self {
        NonNull::new_unchecked(ptr)
    }

    unsafe fn into_raw(self) -> *mut T {
        NonNull::as_ptr(self)
    }
}