//! 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`, 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 where L: LengthType, SM: SpareMemoryPolicy, { arr: [mem::MaybeUninit; C], len: L, phantom: PhantomData, } impl ArrayString where L: LengthType, SM: SpareMemoryPolicy, { /// 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 { 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 { 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;