cds 0.10.0

Collection of Optimized Data Structures
Documentation
//! A string-like array.

use crate::{
    len::{LengthType, Usize},
    mem::{SpareMemoryPolicy, Uninitialized},
};
use core::{marker::PhantomData, mem, ptr, slice};

/// A non-growable array with string-like API.
///
/// Written as `ArrayString<C, L, SM>`, array-string has the capacity of `C` bytes.
///
/// It uses type `L` as [`length type`], and `SM` as [`spare memory policy`].
///
/// Similar to [`str`] `ArrayString` is UTF-8 encoded.
///
/// [`spare memory policy`]: SpareMemoryPolicy
/// [`length type`]: LengthType
pub struct ArrayString<const C: usize, L = Usize, SM = Uninitialized>
where
    L: LengthType,
    SM: SpareMemoryPolicy<u8>,
{
    arr: [mem::MaybeUninit<u8>; C],
    len: L,
    phantom: PhantomData<SM>,
}

impl<L, SM, const C: usize> ArrayString<C, L, SM>
where
    L: LengthType,
    SM: SpareMemoryPolicy<u8>,
{
    /// The capacity of the array-string as associated constant.
    ///
    /// The capacity can also be obtained via the [`capacity`] method.
    ///
    /// # Examples
    /// ```rust
    /// # use cds::{arraystring::ArrayString, len::U8};
    /// type S = ArrayString<8, U8>;
    /// let s = S::new();
    /// assert_eq!(S::CAPACITY, 8);
    /// assert_eq!(s.capacity(), S::CAPACITY);
    /// ```
    ///
    /// [`capacity`]: ArrayString::capacity
    pub const CAPACITY: usize = C;

    /// Returns the capacity of the array-string in bytes.
    ///
    /// This is a convenience method. The capacity of the array-string is known at compilation time
    /// and can be also obtained via the [`CAPACITY`] associated constant.
    ///
    /// # Examples
    ///
    /// ```rust
    /// # use cds::array_str;
    /// let mut s = array_str![17;];
    /// assert_eq!(s.capacity(), 17);
    /// ```
    ///
    /// [`CAPACITY`]: ArrayString::CAPACITY
    #[inline]
    pub fn capacity(&self) -> usize {
        Self::CAPACITY
    }

    /// Returns the length of unused capacity in bytes.
    ///
    /// Equivalent to `capacity() - len()`.
    ///
    /// # Examples
    ///
    /// ```rust
    /// # use cds::array_str;
    /// let mut s = array_str![2;];
    /// assert_eq!(s.capacity(), 2);
    /// assert_eq!(s.spare_capacity(), 2);
    ///
    /// s.push('a');
    /// assert_eq!(s.capacity(), 2);
    /// assert_eq!(s.spare_capacity(), 1);
    /// ```
    #[inline]
    pub fn spare_capacity(&self) -> usize {
        Self::CAPACITY - self.len.as_usize()
    }

    /// Creates a new empty `ArrayString`.
    ///
    /// # Examples
    ///
    /// ```rust
    /// # use cds::{arraystring::ArrayString, len::U8};
    /// type AS = ArrayString<7, U8>;
    /// let s = AS::new();
    /// ```
    #[inline]
    pub fn new() -> Self {
        let mut s = Self::new_raw(0);
        unsafe { SM::init(s.as_mut_ptr(), Self::CAPACITY) };
        s
    }

    #[inline(always)]
    fn new_raw(len: usize) -> Self {
        Self {
            // it is safe to call `assume_init` to create an array of `MaybeUninit`
            arr: unsafe { mem::MaybeUninit::uninit().assume_init() },
            len: L::new(len),
            phantom: PhantomData,
        }
    }

    #[inline]
    fn as_ptr(&self) -> *const u8 {
        self.arr.as_ptr() as *const u8
    }

    #[inline]
    fn as_mut_ptr(&mut self) -> *mut u8 {
        self.arr.as_mut_ptr() as *mut u8
    }

    #[inline]
    unsafe fn set_len(&mut self, new_len: usize) {
        debug_assert!(new_len <= Self::CAPACITY);
        self.len.set(new_len);
    }

    #[inline]
    unsafe fn spare_capacity_mut(&mut self) -> &mut [u8] {
        slice::from_raw_parts_mut(self.as_mut_ptr().add(self.len()), self.spare_capacity())
    }

    /// Returns the length of the array-string in bytes.
    ///
    /// Note that the returned length is in bytes, not chars or graphemes.
    ///
    /// # Examples
    /// ```rust
    /// # use cds::array_str;
    /// let s = array_str![16; "€"];
    /// assert_eq!(s.len(), 3); // the length of array-string's UTF-8 encoding in bytes
    /// ```
    #[inline]
    pub fn len(&self) -> usize {
        self.len.as_usize()
    }

    /// Checks of the `ArrayString` is empty.
    ///
    /// Returns `true` if this `ArrayString` has a length of zero, and `false` otherwise.
    ///
    /// # Examples
    /// ```rust
    /// # use cds::array_str;
    /// let a = array_str![16;];
    /// assert!(a.is_empty());
    /// assert_eq!(a.len(), 0);
    /// ```
    #[inline]
    pub fn is_empty(&self) -> bool {
        self.len() == 0
    }

    /// Returns a byte slice of this `ArrayString`'s contents.
    ///
    /// # Examples
    ///
    /// ```rust
    /// # use cds::array_str;
    /// let s = array_str![16; "cds"];
    /// assert_eq!(s.as_bytes(), &[99, 100, 115]);
    /// ```
    #[inline]
    pub fn as_bytes(&self) -> &[u8] {
        unsafe { slice::from_raw_parts(self.as_ptr(), self.len()) }
    }

    #[inline]
    fn as_bytes_mut(&mut self) -> &mut [u8] {
        unsafe { slice::from_raw_parts_mut(self.as_mut_ptr(), self.len()) }
    }

    /// Extracts a string slice containing the entire `ArrayString`.
    ///
    /// # Examples
    /// ```rust
    /// # use cds::array_str;
    /// let s = array_str![16; "cds"];
    /// assert_eq!(s.as_str(), "cds");
    /// ```
    #[inline]
    pub fn as_str(&self) -> &str {
        unsafe { core::str::from_utf8_unchecked(self.as_bytes()) }
    }

    /// Converts an `ArrayString` into a mutable string slice.
    ///
    /// # Examples
    /// ```rust
    /// # use cds::array_str;
    /// let mut s = array_str![16; "cds"];
    /// assert_eq!(s, "cds");
    ///
    /// s.as_mut_str().make_ascii_uppercase();
    /// assert_eq!(s, "CDS");
    /// ```
    #[inline]
    pub fn as_mut_str(&mut self) -> &mut str {
        unsafe { core::str::from_utf8_unchecked_mut(self.as_bytes_mut()) }
    }

    /// Truncates this `ArrayString`, removing all contents.
    ///
    /// # Examples
    /// ```rust
    /// # use cds::array_str;
    /// let mut s = array_str![16; "cds"];
    /// assert_eq!(s, "cds");
    /// s.clear();
    /// assert_eq!(s, "");
    /// assert!(s.is_empty());
    /// ```
    pub fn clear(&mut self) {
        let len = self.len();
        unsafe {
            self.set_len(0);
            SM::init(self.as_mut_ptr(), len);
        }
    }

    /// Appends a character to the end of this `ArrayString`.
    ///
    /// # Panics
    ///
    /// This method panics if the array-string doesn't have enough spare capacity to
    /// accommodate the UTF-8 encoded character.
    ///
    /// See [`try_push`] for a method that returns [`InsufficientCapacityError`] instead.
    ///
    /// # Examples
    /// ```rust
    /// # use cds::array_str;
    /// let mut s = array_str![8;];
    /// assert_eq!(s, "");
    /// s.push('A');
    /// assert_eq!(s, "A");
    /// ```
    ///
    /// Panics if there is no spare capacity:
    /// ```should_panic
    /// # use cds::array_str;
    /// let mut s = array_str![2; "ab"];
    /// s.push('c');
    /// ```
    ///
    /// [`try_push`]: ArrayString::try_push
    #[inline]
    pub fn push(&mut self, ch: char) {
        if ch.len_utf8() > self.spare_capacity() {
            panic!("insufficient capacity");
        }
        unsafe { self.push_unchecked(ch) };
    }

    /// Tries to append a character to the end of this `ArrayString`.
    ///
    /// This is a non-panic version of [`push`].
    ///
    /// Returns [`InsufficientCapacityError`] if there is no spare capacity to accommodate the UTF-8
    /// encoded character.
    ///
    /// # Examples
    /// ```rust
    /// # use cds::{array_str, arraystring::errors::InsufficientCapacityError};
    /// let mut s = array_str![3; "ab"];
    /// assert!(s.try_push('c').is_ok());
    /// assert!(matches!(s.try_push('d'), Err(e) if e == InsufficientCapacityError));
    /// ```
    ///
    /// [`push`]: ArrayString::push
    #[inline]
    pub fn try_push(&mut self, ch: char) -> Result<(), InsufficientCapacityError> {
        if ch.len_utf8() > self.spare_capacity() {
            return Err(InsufficientCapacityError);
        }
        unsafe { self.push_unchecked(ch) };
        Ok(())
    }

    /// Appends a character to the end of this `ArrayString` without spare capacity check.
    ///
    /// # Safety
    ///
    /// The caller must ensure that the array-string has enough spare capacity to accommodate the
    /// UTF-8 encoded character.
    ///
    /// # Examples
    /// ```rust
    /// # use cds::array_str;
    /// const c: char = 'c';
    /// let mut s = array_str![3; "ab"];
    /// if s.spare_capacity() >= c.len_utf8() {
    ///     unsafe { s.push_unchecked(c) };
    /// }
    /// ```
    #[inline]
    pub unsafe fn push_unchecked(&mut self, ch: char) {
        let len = ch.encode_utf8(self.spare_capacity_mut()).len();
        self.len += len;
    }

    /// Appends a given string slice to the end of this `ArrayString`.
    ///
    /// # Panics
    ///
    /// The method panics if there is no enough spare capacity to accommodate the whole string
    /// slice.
    ///
    /// See [`try_push_str`] for a method that returns [`InsufficientCapacityError`] instead.
    ///
    /// # Examples
    /// ```rust
    /// # use cds::array_str;
    /// let mut s = array_str![16;];
    /// s.push_str("Hello, world!");
    /// assert_eq!("Hello, world!", s);
    /// ```
    ///
    /// Panics if there is no spare capacity:
    /// ```should_panic
    /// # use cds::array_str;
    /// let mut s = array_str![8; "Hello"];
    /// s.push_str(", world!");
    /// ```
    ///
    /// [`try_push_str`]: ArrayString::try_push_str
    #[inline]
    pub fn push_str(&mut self, s: &str) {
        if s.len() > self.spare_capacity() {
            panic!("insufficient capacity");
        }
        unsafe { self.push_str_unchecked(s) };
    }

    /// Appends as much characters of a string slice as spare capacity allows.
    ///
    /// Returns the number of bytes copied. Note that the return value represents the size
    /// of the UTF-8 encoding of the successfully copied characters.
    ///
    /// The difference between [`push_str`] and [`add_str`] is that the former panics if there
    /// is no spare capacity to accommodate the whole string slice, while the latter copies as much
    /// characters as possible.
    ///
    /// # Examples
    /// ```rust
    /// # use cds::array_str;
    /// let mut s = array_str![4;];
    /// assert_eq!(s.add_str("€€"), 3);
    /// assert_eq!(s.add_str("€"), 0);
    /// assert_eq!(s, "€");
    /// ```
    ///
    /// [`push_str`]: ArrayString::push_str
    /// [`add_str`]: ArrayString::add_str
    #[inline]
    pub fn add_str(&mut self, s: &str) -> usize {
        let spare_bytes = self.spare_capacity();
        let mut s_len = s.len();
        if s_len > spare_bytes {
            s_len = spare_bytes;
            while !s.is_char_boundary(s_len) {
                s_len -= 1;
            }
        }
        unsafe {
            let len = self.len();
            ptr::copy_nonoverlapping(s.as_ptr(), self.as_mut_ptr().add(len), s_len);
            self.set_len(len + s_len);
        }
        s_len
    }

    /// Tries to append a given string slice to the end of this `ArrayString`.
    ///
    /// This is a non-panic version of [`push_str`].
    ///
    /// Returns [`InsufficientCapacityError`] if there is no enough spare capacity to accommodate
    /// the whole string slice.
    ///
    /// # Examples
    ///
    /// ```rust
    /// # use cds::{array_str, arraystring::errors::InsufficientCapacityError};
    /// let mut s = array_str![8;];
    /// assert!(s.try_push_str("Hello").is_ok());
    /// assert!(matches!(s.try_push_str(", world!"), Err(e) if e == InsufficientCapacityError));
    /// ```
    ///
    /// [`push_str`]: ArrayString::push_str
    #[inline]
    pub fn try_push_str(&mut self, s: &str) -> Result<(), InsufficientCapacityError> {
        if s.len() > self.spare_capacity() {
            return Err(InsufficientCapacityError);
        }
        unsafe { self.push_str_unchecked(s) };
        Ok(())
    }

    /// Appends a given string slice to the end of this `ArrayString` without spare capacity check.
    ///
    /// # Safety
    ///
    /// The caller must ensure that there is enough spare capacity to push the whole string slice.
    ///
    /// # Examples
    /// ```rust
    /// # use cds::array_str;
    /// const STR: &'static str = ", world!";
    /// let mut s = array_str![16; "Hello"];
    /// if STR.len() <= s.spare_capacity() {
    ///     unsafe { s.push_str_unchecked(STR) };
    /// }
    /// assert_eq!("Hello, world!", s);
    /// ```
    #[inline]
    pub unsafe fn push_str_unchecked(&mut self, s: &str) {
        let len = s.len();
        ptr::copy_nonoverlapping(s.as_ptr(), self.as_mut_ptr().add(self.len()), len);
        self.len += len;
    }

    /// Removes the last character from this `ArrayString` and returns it.
    ///
    /// Returns `None` if this array-string is empty.
    ///
    /// # Examples
    /// ```rust
    /// # use cds::array_str;
    /// let mut s = array_str![8; "cds"];
    /// assert_eq!(s.pop(), Some('s'));
    /// assert_eq!(s.pop(), Some('d'));
    /// assert_eq!(s.pop(), Some('c'));
    /// assert_eq!(s.pop(), None);
    /// ```
    #[inline]
    pub fn pop(&mut self) -> Option<char> {
        let ch = self.chars().rev().next()?;
        let ch_len = ch.len_utf8();
        let new_len = self.len() - ch_len;
        unsafe {
            SM::init(self.as_mut_ptr().add(new_len), ch_len);
            self.set_len(new_len);
        }
        Some(ch)
    }

    /// Inserts a character into this `ArrayString` at a byte position.
    ///
    /// This is an O(n) operation, as it potentially copies all bytes in the array-string.
    ///
    /// # Panics
    ///
    /// This method panics if any of the following conditions is true:
    ///
    /// - `idx` doesn't lie on a [`char`] boundary
    /// - `idx` is greater than array-string's length
    /// - there is no spare capacity to accommodate the UTF-8 encoded character
    ///
    /// See [`try_insert`] for a method that returns [`InsertError`] instead.
    ///
    /// # Examples
    /// ```rust
    /// # use cds::array_str;
    /// let mut s = array_str![8; "ac"];
    /// s.insert(1, 'b');
    /// assert_eq!("abc", s);
    /// ```
    ///
    /// [`try_insert`]: ArrayString::try_insert
    #[inline]
    pub fn insert(&mut self, idx: usize, ch: char) {
        self.try_insert(idx, ch).expect("insert failed")
    }

    /// Tries to insert a character into this `ArrayString` at a byte position.
    ///
    /// This is an O(n) operation, as it potentially copies all bytes in the array-string.
    ///
    /// This is a non-panic version of [`insert`].
    ///
    /// This method returns the following error:
    ///
    /// - [`InsertError::InvalidIndex`] - if `idx` doesn't lie on a [`char`] boundary,
    ///   or `idx > len`
    /// - [`InsertError::InsufficientCapacity`] - if there is no enough spare capacity to
    ///   accommodate the UTF-8 encoded character
    ///
    /// # Examples
    /// ```rust
    /// # use cds::{array_str, arraystring::errors::InsertError};
    /// let mut s = array_str![6; "2"];
    /// assert!(s.try_insert(1, '€').is_ok());
    /// assert_eq!(s, "2€");
    /// assert!(matches!(s.try_insert(2, '5'), Err(InsertError::InvalidIndex))); // not a char boundary
    /// assert!(matches!(s.try_insert(5, '0'), Err(InsertError::InvalidIndex))); // index exceeds length
    /// assert!(matches!(s.try_insert(4, '€'), Err(InsertError::InsufficientCapacity)));
    /// ```
    ///
    /// [`insert`]: ArrayString::insert
    #[inline]
    pub fn try_insert(&mut self, idx: usize, ch: char) -> Result<(), InsertError> {
        if !self.is_char_boundary(idx) {
            return Err(InsertError::InvalidIndex);
        }

        let ch_len = ch.len_utf8();
        if ch_len > self.spare_capacity() {
            return Err(InsertError::InsufficientCapacity);
        }

        unsafe {
            let len = self.len();
            let tgt = self.as_mut_ptr().add(idx);
            ptr::copy(tgt, tgt.add(ch_len), len - idx);
            ch.encode_utf8(slice::from_raw_parts_mut(tgt, ch_len));
            self.set_len(len + ch_len);
        }

        Ok(())
    }

    /// Inserts a string slice into this `ArrayString` at a byte position.
    ///
    /// This is an O(n) operation, as it potentially copies all bytes in the array-string.
    ///
    /// # Panics
    ///
    /// This method panics if any of the following conditions is true:
    ///
    /// - `idx` doesn't lie on a [`char`] boundary
    /// - `idx` is greater then array-string length
    /// - there is no enough spare capacity to accommodate the whole string slice
    ///
    /// See [`try_insert_str`] for a method that returns [`InsertError`] instead.
    ///
    /// # Examples
    ///
    /// ```rust
    /// # use cds::array_str;
    /// let mut s = array_str![8; "ds"];
    /// s.insert_str(0, "c");
    /// assert_eq!(s, "cds");
    /// ```
    ///
    /// [`try_insert_str`]: ArrayString::try_insert_str
    #[inline]
    pub fn insert_str(&mut self, idx: usize, s: &str) {
        self.try_insert_str(idx, s).expect("insert_str failed")
    }

    /// Tries to insert a string slice into this `ArrayString` at a byte position.
    ///
    /// This is an O(n) operation, as it potentially copies all bytes in the array-string.
    ///
    /// This is a non-panic version of [`insert_str`].
    ///
    /// # Examples
    ///
    /// ```rust
    /// # use cds::{array_str, arraystring::errors::InsertError};
    /// let mut s = array_str![5; "2"];
    /// assert!(s.try_insert_str(1, "€").is_ok());
    /// assert_eq!(s, "2€");
    /// assert!(matches!(s.try_insert_str(2, "a"), Err(InsertError::InvalidIndex)));
    /// assert!(matches!(s.try_insert_str(5, "a"), Err(InsertError::InvalidIndex)));
    /// assert!(matches!(s.try_insert_str(4, "€"), Err(InsertError::InsufficientCapacity)));
    /// ```
    ///
    /// [`insert_str`]: ArrayString::insert_str
    #[inline]
    pub fn try_insert_str(&mut self, idx: usize, s: &str) -> Result<(), InsertError> {
        if !self.is_char_boundary(idx) {
            return Err(InsertError::InvalidIndex);
        }

        let s_len = s.len();
        if s_len > self.spare_capacity() {
            return Err(InsertError::InsufficientCapacity);
        }

        unsafe {
            let len = self.len();
            let tgt = self.as_mut_ptr().add(idx);
            ptr::copy(tgt, tgt.add(s_len), len - idx);
            ptr::copy_nonoverlapping(s.as_ptr(), tgt, s_len);
            self.set_len(len + s_len);
        }

        Ok(())
    }

    /// Removes a [`char`] from the `ArrayString` at a byte position and returns it.
    ///
    /// This is an O(n) operation, as it potentially copies all bytes in the array-string.
    ///
    /// # Panics
    ///
    /// This method panics if any of the following conditions is true:
    ///
    /// - `idx` doesn't lie on a [`char`] boundary
    /// - `idx` is greater than or equal the array-string length
    ///
    /// See [`try_remove`] for a method that returns [`IndexError`] instead.
    ///
    /// # Examples
    ///
    /// ```rust
    /// # use cds::array_str;
    /// let mut s = array_str![8; "2€ "];
    /// assert_eq!(s.remove(1), '€');
    /// assert_eq!(s, "2 ");
    /// ```
    ///
    /// [`try_remove`]: ArrayString::try_remove
    #[inline]
    pub fn remove(&mut self, idx: usize) -> char {
        self.try_remove(idx).expect("invalid index")
    }

    /// Tries to remove a [`char`] from the `ArrayString` at a byte position and returns it.
    ///
    /// This is an O(n) operation, as it potentially copies all bytes in the array-string.
    ///
    /// This is a non-panic version of [`remove`].
    ///
    /// # Examples
    ///
    /// ```rust
    /// # use cds::{array_str, arraystring::errors::IndexError};
    /// # fn foo() -> Result<(), IndexError> {
    /// let mut s = array_str![4; "2€"];
    /// for i in 2..=5 {
    ///     assert!(matches!(s.try_remove(i), Err(IndexError)));
    /// }
    /// assert_eq!(s.try_remove(0)?, '2');
    /// assert_eq!(s, "€");
    /// # Ok(())
    /// # }
    /// # foo().unwrap();
    /// ```
    ///
    /// [`remove`]: ArrayString::remove
    #[inline]
    pub fn try_remove(&mut self, idx: usize) -> Result<char, IndexError> {
        if !self.is_char_boundary(idx) {
            return Err(IndexError);
        }

        let ch = match self[idx..].chars().next() {
            Some(ch) => ch,
            None => return Err(IndexError),
        };

        let len = self.len();
        let ch_len = ch.len_utf8();
        let new_len = len - ch_len;
        let to_copy_len = new_len - idx;

        unsafe {
            let tgt = self.as_mut_ptr().add(idx);
            ptr::copy(tgt.add(ch_len), tgt, to_copy_len);
            SM::init(tgt.add(to_copy_len), ch_len);
            self.set_len(new_len);
        }

        Ok(ch)
    }

    /// Truncates the `ArrayString` to a specified length in bytes.
    ///
    /// If `new_len` is equal or greater than current array-string length, this method does nothing.
    ///
    /// # Panics
    ///
    /// This method panics if `new_len` doesn't lie on a [`char`] boundary.
    ///
    /// See [`try_truncate`] for a method that returns [`IndexError`] instead.
    ///
    /// # Examples
    ///
    /// ```rust
    /// # use cds::array_str;
    /// let mut s = array_str![4; "cds"];
    /// s.truncate(1);
    /// assert_eq!(s, "c");
    /// ```
    ///
    /// [`try_truncate`]: ArrayString::try_truncate
    #[inline]
    pub fn truncate(&mut self, new_len: usize) {
        self.try_truncate(new_len).expect("truncate failed")
    }

    /// Tries to truncate the `ArrayString` to a specified length in bytes.
    ///
    /// If `new_len` is equal or greater than current array-string length, this method does nothing.
    ///
    /// This is a non-panic version of [`truncate`].
    ///
    /// This method returns [`IndexError`] if `new_len` doesn't lie on a [`char`] boundary.
    ///
    /// # Examples
    ///
    /// ```rust
    /// # use cds::{array_str, arraystring::errors::IndexError};
    /// let mut s = array_str![8; "2€"];
    /// assert!(matches!(s.try_truncate(2), Err(IndexError))); // <-- 2 is not a char boundary
    /// assert!(s.try_truncate(4).is_ok());  // <-- new_len equals the current array-string length
    /// assert_eq!(s, "2€");
    /// assert!(s.try_truncate(1).is_ok());
    /// assert_eq!(s, "2");
    /// ```
    ///
    /// [`truncate`]: ArrayString::truncate
    #[inline]
    pub fn try_truncate(&mut self, new_len: usize) -> Result<(), IndexError> {
        let len = self.len();
        if new_len >= len {
            return Ok(());
        }

        if !self.is_char_boundary(new_len) {
            return Err(IndexError);
        }

        unsafe {
            self.set_len(new_len);
            SM::init(self.as_mut_ptr().add(new_len), len - new_len);
        }

        Ok(())
    }
}

pub mod errors;
use errors::*;

mod format;
pub use format::*;

mod macros;
mod traits;

#[cfg(test)]
mod test_arraystring;