turbocow 0.3.0-beta.2

Compact, clone-on-write vectors, strings, maps and sets with inline + referenced storage — a superset of ecow.
Documentation
use core::mem::ManuallyDrop;

use crate::utils::variance::PhantomCovariantLifetime;

use crate::allocator::{AllocatorProvider, Global};
use crate::vec::types::EcoVec;

use super::common::limit::LIMIT;

/// A byte vector that can hold up to 15 bytes inline and then spills to an
/// `EcoVec<u8, A>` with allocator support.
///
/// The const generic `N` controls the inline buffer size. For the default
/// `Global` allocator this is [`LIMIT`] (typically 15 bytes). Custom
/// allocators can use [`inline_limit`](super::common::limit::inline_limit)
/// to compute the correct value.
///
/// The lifetime `'a` tracks borrowed data in the [`Referenced`] variant.
/// Use `'static` (the common case, e.g. in [`EcoString`](crate::EcoString))
/// when the vector always owns its data.
pub(crate) struct DynamicVec<'a, const N: usize = LIMIT, A = Global>(
    pub(super) Repr<'a, N, A>,
)
where
    A: AllocatorProvider;

#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub(crate) struct InlineVec<const N: usize = LIMIT, A = Global>
where
    A: AllocatorProvider,
{
    /// Inline byte buffer.
    pub(super) buf: [u8; N],
    /// Invariant: After masking off the tag, never exceeds N.
    pub(super) tagged_len: u8,
    /// Allocator used when this buffer spills to the heap.
    pub(super) allocator: A,
}

/// A zero-copy borrowed byte slice stored inside the [`DynamicVec`] union.
///
/// The tag bits (`0b11xx_xxxx`) are embedded in the high byte of `len` so
/// that reading `tagged_len` through the [`InlineVec`] union member detects
/// this variant correctly.  On architectures where the `len` field does
/// **not** overlap with `tagged_len` (e.g. 32-bit), the constructor
/// additionally writes the tag byte explicitly.
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub(crate) struct Referenced<'a, A = Global>
where
    A: AllocatorProvider,
{
    /// Raw pointer to the borrowed data.
    pub(super) ptr: *const u8,
    /// Length with tag bits embedded in the two highest bits.
    pub(super) len: usize,
    /// Ties the borrow lifetime to this struct (covariant in `'a`).
    pub(super) _lifetime: PhantomCovariantLifetime<'a>,
    /// Allocator carried along so spilling can use it.
    pub(super) allocator: A,
}

// Safety: Referenced is just a read-only view + Copy allocator.
unsafe impl<A: AllocatorProvider + Send> Send for Referenced<'_, A> {}
unsafe impl<A: AllocatorProvider + Sync> Sync for Referenced<'_, A> {}

/// The internal representation.
///
/// On 64-bit little endian, this assumes that no valid EcoVec exists in which
/// the highest-order bit of the InlineVec's `tagged_len` would be set. This is
/// true because EcoVec is repr(C) and its second field `len` is bounded by
/// `isize::MAX`. On 32-bit, it's no problem and for 64-bit big endian, we
/// have an increased limit to prevent the overlap.
#[repr(C)]
pub(super) union Repr<'a, const N: usize, A>
where
    A: AllocatorProvider,
{
    pub(super) referenced: ManuallyDrop<Referenced<'a, A>>,
    pub(super) inline: ManuallyDrop<InlineVec<N, A>>,
    pub(super) spilled: ManuallyDrop<EcoVec<u8, A>>,
}

// ---------------------------------------------------------------------------
// Compile-time layout assertions for the default (Global) allocator.
//
// These verify the invariants that the tag-byte scheme depends on:
//
// 1. InlineVec and EcoVec<u8> have the same size (union members overlap
//    correctly).
// 2. Referenced fits within the union (no larger than InlineVec).
// 3. The `tagged_len` byte sits at offset LIMIT (= N) inside InlineVec.
//    For repr(C) with fields `buf: [u8; N]`, `tagged_len: u8`, `allocator: A`,
//    the offset of `tagged_len` is exactly N (no padding before it since
//    the preceding field is [u8; N] with align 1).
// 4. LIMIT (inline capacity) is at most 63, so it fits in the 6-bit value
//    field of the tagged byte.
// ---------------------------------------------------------------------------
const _: () = {
    use core::mem::size_of;

    // (1) InlineVec<LIMIT, Global> must be the same size as EcoVec<u8, Global>
    assert!(
        size_of::<InlineVec<LIMIT, Global>>() == size_of::<EcoVec<u8, Global>>(),
        "InlineVec and EcoVec<u8> must have the same size for the union to work"
    );

    // (2) Referenced must fit within the union
    assert!(
        size_of::<Referenced<'static, Global>>() <= size_of::<InlineVec<LIMIT, Global>>(),
        "Referenced must not be larger than InlineVec"
    );

    // (3) tagged_len is at offset N in InlineVec<N, Global>.
    //     InlineVec is repr(C) with fields: buf: [u8; N], tagged_len: u8, allocator: A.
    //     Since [u8; N] has alignment 1, tagged_len follows immediately at offset N.
    //     We verify this with size_of since all fields before tagged_len are [u8; N].
    assert!(size_of::<[u8; LIMIT]>() == LIMIT, "tagged_len offset must equal LIMIT");

    // (4) LIMIT fits in the 6-bit value field (max 63)
    assert!(LIMIT <= 63, "LIMIT must be at most 63 to fit in the 6-bit value field");

    // (5) Global allocator is ZST (inline path assumes this)
    assert!(size_of::<Global>() == 0, "Global allocator must be zero-sized");
};

/// This is never stored in memory, it's just an abstraction for safe access.
#[derive(Debug)]
pub(super) enum Variant<'a, 'v, const N: usize, A>
where
    A: AllocatorProvider,
{
    Referenced(&'v Referenced<'a, A>),
    Inline(&'v InlineVec<N, A>),
    Spilled(&'v EcoVec<u8, A>),
}

/// This is never stored in memory, it's just an abstraction for safe access.
#[derive(Debug)]
pub(super) enum VariantMut<'a, 'v, const N: usize, A>
where
    A: AllocatorProvider,
{
    Referenced(&'v mut Referenced<'a, A>),
    Inline(&'v mut InlineVec<N, A>),
    Spilled(&'v mut EcoVec<u8, A>),
}