use std::fmt; pub(crate) type Len = u8; /// Fixed-size stack-allocated string #[derive(Copy, Clone)] pub struct StackString { len: Len, buffer: StrBuffer, } impl StackString { pub const CAPACITY: usize = CAPACITY; pub const EMPTY: Self = Self::empty(); const fn empty() -> Self { Self { len: 0, buffer: StrBuffer::empty(), } } /// Create a `StackString` from a `&str`, if it'll fit within `Self::CAPACITY` /// /// # Examples /// /// Basic usage: /// /// ``` /// let s = kstring::StackString::<3>::try_new("foo"); /// assert_eq!(s.as_deref(), Some("foo")); /// let s = kstring::StackString::<3>::try_new("foobar"); /// assert_eq!(s, None); /// ``` #[inline] #[must_use] pub fn try_new(s: &str) -> Option { let len = s.as_bytes().len(); if len <= Self::CAPACITY { #[cfg(feature = "unsafe")] let stack = { unsafe { // SAFETY: We've confirmed `len` is within size Self::new_unchecked(s) } }; #[cfg(not(feature = "unsafe"))] let stack = { Self::new(s) }; Some(stack) } else { None } } /// Create a `StackString` from a `&str` /// /// # Panic /// /// Calling this function with a string larger than `Self::CAPACITY` will panic /// /// # Examples /// /// Basic usage: /// /// ``` /// let s = kstring::StackString::<3>::new("foo"); /// assert_eq!(s, "foo"); /// ``` #[inline] #[must_use] pub fn new(s: &str) -> Self { let len = s.as_bytes().len() as u8; debug_assert!(Self::CAPACITY <= Len::MAX.into()); let buffer = StrBuffer::new(s); Self { len, buffer } } /// Create a `StackString` from a `&str` /// /// # Safety /// /// Calling this function with a string larger than `Self::CAPACITY` is undefined behavior. /// /// # Examples /// /// Basic usage: /// /// ``` /// let s = unsafe { /// // SAFETY: Literal is short-enough /// kstring::StackString::<3>::new_unchecked("foo") /// }; /// assert_eq!(s, "foo"); /// ``` #[inline] #[must_use] #[cfg(feature = "unsafe")] pub unsafe fn new_unchecked(s: &str) -> Self { let len = s.as_bytes().len() as u8; debug_assert!(Self::CAPACITY <= Len::MAX.into()); let buffer = StrBuffer::new_unchecked(s); Self { len, buffer } } /// Extracts a string slice containing the entire `StackString`. /// /// # Examples /// /// Basic usage: /// /// ``` /// let s = kstring::StackString::<3>::try_new("foo").unwrap(); /// /// assert_eq!("foo", s.as_str()); /// ``` #[inline] #[must_use] pub fn as_str(&self) -> &str { let len = self.len as usize; #[cfg(feature = "unsafe")] unsafe { // SAFETY: Constructors guarantee that `buffer[..len]` is a `str`, // and we don't mutate the data afterwards. self.buffer.as_str_unchecked(len) } #[cfg(not(feature = "unsafe"))] self.buffer.as_str(len) } /// Converts a `StackString` into a mutable string slice. /// /// # Examples /// /// Basic usage: /// /// ``` /// let mut s = kstring::StackString::<6>::try_new("foobar").unwrap(); /// let s_mut_str = s.as_mut_str(); /// /// s_mut_str.make_ascii_uppercase(); /// /// assert_eq!("FOOBAR", s_mut_str); /// ``` #[inline] #[must_use] pub fn as_mut_str(&mut self) -> &mut str { let len = self.len as usize; #[cfg(feature = "unsafe")] unsafe { // SAFETY: Constructors guarantee that `buffer[..len]` is a `str`, // and we don't mutate the data afterwards. self.buffer.as_mut_str_unchecked(len) } #[cfg(not(feature = "unsafe"))] self.buffer.as_mut_str(len) } /// Returns the length of this `StasckString`, in bytes, not [`char`]s or /// graphemes. In other words, it might not be what a human considers the /// length of the string. /// /// # Examples /// /// Basic usage: /// /// ``` /// let a = kstring::StackString::<3>::try_new("foo").unwrap(); /// assert_eq!(a.len(), 3); /// /// let fancy_f = kstring::StackString::<4>::try_new("ƒoo").unwrap(); /// assert_eq!(fancy_f.len(), 4); /// assert_eq!(fancy_f.chars().count(), 3); /// ``` #[inline] #[must_use] pub fn len(&self) -> usize { self.len as usize } /// Returns `true` if this `StackString` has a length of zero, and `false` otherwise. /// /// # Examples /// /// Basic usage: /// /// ``` /// let mut v = kstring::StackString::<20>::EMPTY; /// assert!(v.is_empty()); /// /// let a = kstring::StackString::<3>::try_new("foo").unwrap(); /// assert!(!a.is_empty()); /// ``` #[inline] #[must_use] pub fn is_empty(&self) -> bool { self.len() == 0 } /// Truncates this `StackString`, removing all contents. /// /// # Examples /// /// Basic usage: /// /// ``` /// let mut s = kstring::StackString::<3>::try_new("foo").unwrap(); /// /// s.clear(); /// /// assert!(s.is_empty()); /// assert_eq!(0, s.len()); /// ``` #[inline] pub fn clear(&mut self) { self.len = 0; } /// Shortens this `StackString` to the specified length. /// /// If `new_len` is greater than the string's current length, this has no /// effect. /// /// Note that this method has no effect on the allocated capacity /// of the string /// /// # Panics /// /// Panics if `new_len` does not lie on a [`char`] boundary. /// /// # Examples /// /// Basic usage: /// /// ``` /// let mut s = kstring::StackString::<5>::try_new("hello").unwrap(); /// /// s.truncate(2); /// /// assert_eq!(s, "he"); /// ``` #[inline] pub fn truncate(&mut self, new_len: usize) { if new_len <= self.len() { assert!(self.is_char_boundary(new_len)); self.len = new_len as u8; } } } impl Default for StackString { fn default() -> Self { Self::empty() } } impl std::ops::Deref for StackString { type Target = str; #[inline] fn deref(&self) -> &str { self.as_str() } } impl Eq for StackString {} impl PartialEq> for StackString { #[inline] fn eq(&self, other: &StackString) -> bool { PartialEq::eq(self.as_str(), other.as_str()) } } impl PartialEq for StackString { #[inline] fn eq(&self, other: &str) -> bool { PartialEq::eq(self.as_str(), other) } } impl<'s, const CAPACITY: usize> PartialEq<&'s str> for StackString { #[inline] fn eq(&self, other: &&str) -> bool { PartialEq::eq(self.as_str(), *other) } } impl PartialEq for StackString { #[inline] fn eq(&self, other: &String) -> bool { PartialEq::eq(self.as_str(), other.as_str()) } } impl Ord for StackString { #[inline] fn cmp(&self, other: &Self) -> std::cmp::Ordering { self.as_str().cmp(other.as_str()) } } impl PartialOrd> for StackString { #[inline] fn partial_cmp(&self, other: &StackString) -> Option { self.as_str().partial_cmp(other.as_str()) } } impl PartialOrd for StackString { #[inline] fn partial_cmp(&self, other: &str) -> Option { self.as_str().partial_cmp(other) } } impl<'s, const CAPACITY: usize> PartialOrd<&'s str> for StackString { #[inline] fn partial_cmp(&self, other: &&str) -> Option { self.as_str().partial_cmp(other) } } impl PartialOrd for StackString { #[inline] fn partial_cmp(&self, other: &String) -> Option { self.as_str().partial_cmp(other.as_str()) } } impl std::hash::Hash for StackString { #[inline] fn hash(&self, state: &mut H) { self.as_str().hash(state); } } impl fmt::Debug for StackString { #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(self.as_str(), f) } } impl fmt::Display for StackString { #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(self.as_str(), f) } } impl AsRef for StackString { #[inline] fn as_ref(&self) -> &str { self.as_str() } } impl AsRef<[u8]> for StackString { #[inline] fn as_ref(&self) -> &[u8] { self.as_bytes() } } impl AsRef for StackString { #[inline] fn as_ref(&self) -> &std::ffi::OsStr { (&**self).as_ref() } } impl AsRef for StackString { #[inline] fn as_ref(&self) -> &std::path::Path { std::path::Path::new(self) } } impl std::borrow::Borrow for StackString { #[inline] fn borrow(&self) -> &str { self.as_str() } } #[derive(Copy, Clone)] #[repr(transparent)] pub(crate) struct StrBuffer([u8; CAPACITY]); impl StrBuffer { pub(crate) const fn empty() -> Self { let array = [0; CAPACITY]; StrBuffer(array) } #[inline] pub(crate) fn new(s: &str) -> Self { let len = s.as_bytes().len(); debug_assert!(len <= CAPACITY); let mut buffer = Self::default(); if let Some(buffer) = buffer.0.get_mut(..len) { buffer.copy_from_slice(s.as_bytes()); } else { panic!("`{}` is larger than capacity {}", s, CAPACITY); } buffer } #[inline] #[cfg(not(feature = "unsafe"))] pub(crate) fn as_str(&self, len: usize) -> &str { let slice = self.0.get(..len).unwrap(); std::str::from_utf8(slice).unwrap() } #[inline] #[cfg(not(feature = "unsafe"))] pub(crate) fn as_mut_str(&mut self, len: usize) -> &mut str { let slice = self.0.get_mut(..len).unwrap(); std::str::from_utf8_mut(slice).unwrap() } } impl StrBuffer { #[inline] #[cfg(feature = "unsafe")] pub(crate) unsafe fn new_unchecked(s: &str) -> Self { let len = s.as_bytes().len(); debug_assert!(len <= CAPACITY); let mut buffer = Self::default(); buffer .0 .get_unchecked_mut(..len) .copy_from_slice(s.as_bytes()); buffer } #[inline] #[cfg(feature = "unsafe")] pub(crate) unsafe fn as_str_unchecked(&self, len: usize) -> &str { let slice = self.0.get_unchecked(..len); std::str::from_utf8_unchecked(slice) } #[inline] #[cfg(feature = "unsafe")] pub(crate) unsafe fn as_mut_str_unchecked(&mut self, len: usize) -> &mut str { let slice = self.0.get_unchecked_mut(..len); std::str::from_utf8_unchecked_mut(slice) } } impl Default for StrBuffer { fn default() -> Self { Self::empty() } }