From 4d4e4486d140697d1d7805df027e4a22442995d0 Mon Sep 17 00:00:00 2001 From: Pantonshire Date: Thu, 14 Jul 2022 11:14:45 +0100 Subject: [PATCH] ShString is now InliningString --- src/strings/{inline.rs => capped.rs} | 62 ++--- src/strings/inlining.rs | 401 +++++++++++++++++++++++++++ src/strings/mod.rs | 8 +- src/strings/shstring.rs | 371 ------------------------- 4 files changed, 436 insertions(+), 406 deletions(-) rename src/strings/{inline.rs => capped.rs} (76%) create mode 100644 src/strings/inlining.rs delete mode 100644 src/strings/shstring.rs diff --git a/src/strings/inline.rs b/src/strings/capped.rs similarity index 76% rename from src/strings/inline.rs rename to src/strings/capped.rs index eb04079..7b967d5 100644 --- a/src/strings/inline.rs +++ b/src/strings/capped.rs @@ -9,12 +9,12 @@ use std::{ }; #[derive(Clone)] -pub struct InlineString { +pub struct CappedString { buf: [u8; N], len: u8, } -impl InlineString { +impl CappedString { const MAX_LEN: u8 = { #[allow(clippy::cast_possible_truncation, clippy::checked_conversions)] if N <= u8::MAX as usize { @@ -24,7 +24,7 @@ impl InlineString { } }; - /// Creates a new `InlineString` from a given byte buffer and length. + /// Creates a new `CappedString` from a given byte buffer and length. /// /// # Safety /// @@ -35,7 +35,7 @@ impl InlineString { Self { buf, len } } - /// Returns a new empty `InlineString`. + /// Returns a new empty `CappedString`. #[inline] #[must_use] pub const fn empty() -> Self { @@ -76,24 +76,24 @@ impl InlineString { /// Returns a string slice pointing to the underlying string data. pub fn as_str(&self) -> &str { // SAFETY: - // `len` being less than or equal to `N` is an invariant of `InlineString`, so it is + // `len` being less than or equal to `N` is an invariant of `CappedString`, so it is // always within the bounds of `buf`. let slice = unsafe { self.buf.get_unchecked(..usize::from(self.len)) }; // SAFETY: - // The first `len` bytes of `buf` being valid UTF-8 is an invariant of `InlineString`. + // The first `len` bytes of `buf` being valid UTF-8 is an invariant of `CappedString`. unsafe { str::from_utf8_unchecked(slice) } } /// Returns a mutable string slice pointing to the underlying string data. pub fn as_str_mut(&mut self) -> &mut str { // SAFETY: - // `len` being less than or equal to `N` is an invariant of `InlineString`, so it is + // `len` being less than or equal to `N` is an invariant of `CappedString`, so it is // always within the bounds of `buf`. let slice = unsafe { self.buf.get_unchecked_mut(..usize::from(self.len)) }; // SAFETY: - // The first `len` bytes of `buf` being valid UTF-8 is an invariant of `InlineString`. + // The first `len` bytes of `buf` being valid UTF-8 is an invariant of `CappedString`. unsafe { str::from_utf8_unchecked_mut(slice) } } @@ -110,14 +110,14 @@ impl InlineString { } } -impl Default for InlineString { +impl Default for CappedString { #[inline] fn default() -> Self { Self::empty() } } -impl ops::Deref for InlineString { +impl ops::Deref for CappedString { type Target = str; #[inline] @@ -126,42 +126,42 @@ impl ops::Deref for InlineString { } } -impl ops::DerefMut for InlineString { +impl ops::DerefMut for CappedString { #[inline] fn deref_mut(&mut self) -> &mut Self::Target { self.as_str_mut() } } -impl AsRef for InlineString { +impl AsRef for CappedString { #[inline] fn as_ref(&self) -> &str { self } } -impl AsMut for InlineString { +impl AsMut for CappedString { #[inline] fn as_mut(&mut self) -> &mut str { self } } -impl borrow::Borrow for InlineString { +impl borrow::Borrow for CappedString { #[inline] fn borrow(&self) -> &str { self } } -impl borrow::BorrowMut for InlineString { +impl borrow::BorrowMut for CappedString { #[inline] fn borrow_mut(&mut self) -> &mut str { self } } -impl<'a, const N: usize> TryFrom<&'a str> for InlineString { +impl<'a, const N: usize> TryFrom<&'a str> for CappedString { type Error = Error; #[inline] @@ -170,7 +170,7 @@ impl<'a, const N: usize> TryFrom<&'a str> for InlineString { } } -impl TryFrom for InlineString { +impl TryFrom for CappedString { type Error = Error; #[inline] @@ -179,7 +179,7 @@ impl TryFrom for InlineString { } } -impl<'a, const N: usize> TryFrom> for InlineString { +impl<'a, const N: usize> TryFrom> for CappedString { type Error = Error; #[inline] @@ -188,44 +188,44 @@ impl<'a, const N: usize> TryFrom> for InlineString { } } -impl From> for String { +impl From> for String { #[inline] - fn from(s: InlineString) -> Self { + fn from(s: CappedString) -> Self { s.into_string() } } -impl PartialEq> for InlineString { +impl PartialEq> for CappedString { #[inline] - fn eq(&self, other: &InlineString) -> bool { + fn eq(&self, other: &CappedString) -> bool { **self == **other } } -impl Eq for InlineString {} +impl Eq for CappedString {} -impl PartialOrd> for InlineString { +impl PartialOrd> for CappedString { #[inline] - fn partial_cmp(&self, other: &InlineString) -> Option { + fn partial_cmp(&self, other: &CappedString) -> Option { (**self).partial_cmp(&**other) } } -impl Ord for InlineString { +impl Ord for CappedString { #[inline] fn cmp(&self, other: &Self) -> Ordering { (**self).cmp(&**other) } } -impl Hash for InlineString { +impl Hash for CappedString { #[inline] fn hash(&self, state: &mut H) { (**self).hash(state); } } -impl str::FromStr for InlineString { +impl str::FromStr for CappedString { type Err = Error; #[inline] @@ -234,14 +234,14 @@ impl str::FromStr for InlineString { } } -impl fmt::Debug for InlineString { +impl fmt::Debug for CappedString { #[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Debug::fmt(&**self, f) } } -impl fmt::Display for InlineString { +impl fmt::Display for CappedString { #[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&**self, f) @@ -268,7 +268,7 @@ impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, - "string of length {} exceeds limit for `InlineString<{}>`", + "string of length {} exceeds limit for `CappedString<{}>`", self.actual_len, self.max_len ) diff --git a/src/strings/inlining.rs b/src/strings/inlining.rs new file mode 100644 index 0000000..301071d --- /dev/null +++ b/src/strings/inlining.rs @@ -0,0 +1,401 @@ +use std::{ + borrow::{self, Cow}, + cmp::Ordering, + convert::Infallible, + fmt, + hash::{Hash, Hasher}, + ops, + str::FromStr, +}; + +use super::CappedString; + +/// A non-growable string where strings 22 bytes or shorter are stored inline and longer strings +/// use a separate heap allocation. If maximum inline lengths other than 22 are desired, see the +/// more general [InliningString]. +/// +/// 22 bytes is chosen because it is optimal for 64-bit architectures; the minimum possible size +/// of the data structure on 64-bit architectures which always keeps the data properly aligned is +/// 24 bytes (because, when heap-allocated, the data structure contains a 16-byte `Box<[u8]>` with +/// 8-byte alignment and a 1-byte discriminant, and the greatest multiple of 8 which is ≥17 is 24), +/// and the inline variant needs to use 2 bytes for the length and disciminant. +pub type InliningString22 = InliningString<22>; + +/// A non-growable string which stores small strings inline; strings of length less than or equal +/// to `N` are stored inside the data structure itself, whereas strings of length greater than `N` +/// use a separate heap allocation. +/// +/// This type is intended to be used when lots of small strings need to be stored, and these +/// strings do not need to grow. +/// +/// For 64-bit targets, `N = 22` allows the greatest amount of inline string data to be stored +/// without exceeding the size of a regular [String]. Therefore, [InliningString22] is provided as +/// a type alias for `InliningString<22>`. +/// +/// Although `N` is a `usize`, it may be no greater than `u8::MAX`; larger values will result in a +/// compile-time error. +/// +/// ``` +/// # use libshire::strings::InliningString; +/// let s1 = InliningString::<22>::new("Hello, InliningString!"); +/// assert_eq!(&*s1, "Hello, InliningString!"); +/// assert!(!s1.heap_allocated()); +/// +/// let s2 = InliningString::<22>::new("This string is 23 bytes"); +/// assert_eq!(&*s2, "This string is 23 bytes"); +/// assert!(s2.heap_allocated()); +/// ``` +#[derive(Clone)] +pub struct InliningString(Repr); + +impl InliningString { + /// Creates a new `InliningString` from the given string, storing the string data inline if + /// possible or creating a new heap allocation otherwise. + /// + /// ``` + /// # use libshire::strings::InliningString; + /// let s = InliningString::<22>::new("Hello, InliningString!"); + /// assert_eq!(&*s, "Hello, InliningString!"); + /// ``` + #[inline] + #[must_use] + pub fn new(s: S) -> Self + where + S: AsRef, + Box: From, + { + match CappedString::new(&s) { + Ok(buf) => Self(Repr::Inline(buf)), + Err(_) => Self(Repr::Boxed(Box::::from(s))), + } + } + + /// Returns a new empty `InliningString`. + /// + /// ``` + /// # use libshire::strings::InliningString; + /// let s = InliningString::<22>::empty(); + /// assert_eq!(&*s, ""); + /// ``` + #[inline] + #[must_use] + pub const fn empty() -> Self { + Self(Repr::Inline(CappedString::empty())) + } + + /// Returns a string slice for the underlying string data. + #[inline] + #[must_use] + pub fn as_str(&self) -> &str { + match self { + Self(Repr::Inline(buf)) => buf, + Self(Repr::Boxed(buf)) => buf, + } + } + + /// Returns a mutable string slice for the underlying string data. + #[inline] + #[must_use] + pub fn as_str_mut(&mut self) -> &mut str { + match self { + Self(Repr::Inline(buf)) => buf, + Self(Repr::Boxed(buf)) => buf, + } + } + + /// Consumes the `InliningString` and converts it to a heap-allocated `String`. + #[inline] + #[must_use] + pub fn into_string(self) -> String { + match self { + Self(Repr::Inline(buf)) => buf.into_string(), + Self(Repr::Boxed(buf)) => buf.into_string(), + } + } + + /// Returns the length of the string in bytes. + /// + /// ``` + /// # use libshire::strings::InliningString; + /// let s = InliningString::<22>::new("こんにちは"); + /// assert_eq!(s.len(), 15); + /// ``` + #[inline] + #[must_use] + pub fn len(&self) -> usize { + match self { + Self(Repr::Inline(buf)) => buf.len(), + Self(Repr::Boxed(buf)) => buf.len(), + } + } + + /// Returns `true` if the string has length 0. + /// + /// ``` + /// # use libshire::strings::InliningString; + /// let s1 = InliningString::<22>::new(""); + /// assert!(s1.is_empty()); + /// + /// let s2 = InliningString::<22>::new("Hello"); + /// assert!(!s2.is_empty()); + /// ``` + #[inline] + #[must_use] + pub fn is_empty(&self) -> bool { + match self { + Self(Repr::Inline(buf)) => buf.is_empty(), + Self(Repr::Boxed(buf)) => buf.is_empty(), + } + } + + /// Returns `true` if the string data is stored on the heap, and `false` otherwise. + /// + /// ``` + /// # use libshire::strings::InliningString; + /// let s1 = InliningString::<22>::new("This string's 22 bytes"); + /// assert!(!s1.heap_allocated()); + /// + /// let s2 = InliningString::<22>::new("This string is 23 bytes"); + /// assert!(s2.heap_allocated()); + /// ``` + #[inline] + #[must_use] + pub fn heap_allocated(&self) -> bool { + match self { + Self(Repr::Inline(_)) => false, + Self(Repr::Boxed(_)) => true, + } + } +} + +impl Default for InliningString { + #[inline] + fn default() -> Self { + Self::empty() + } +} + +impl ops::Deref for InliningString { + type Target = str; + + #[inline] + fn deref(&self) -> &Self::Target { + self.as_str() + } +} + +impl ops::DerefMut for InliningString { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + self.as_str_mut() + } +} + +impl AsRef for InliningString { + #[inline] + fn as_ref(&self) -> &str { + self + } +} + +impl AsMut for InliningString { + #[inline] + fn as_mut(&mut self) -> &mut str { + self + } +} + +impl borrow::Borrow for InliningString { + #[inline] + fn borrow(&self) -> &str { + self + } +} + +impl borrow::BorrowMut for InliningString { + #[inline] + fn borrow_mut(&mut self) -> &mut str { + self + } +} + +impl<'a, const N: usize> From<&'a str> for InliningString { + #[inline] + fn from(s: &'a str) -> Self { + Self::new(s) + } +} + +impl From for InliningString { + #[inline] + fn from(s: String) -> Self { + Self::new(s) + } +} + +impl<'a, const N: usize> From> for InliningString { + #[inline] + fn from(s: Cow<'a, str>) -> Self { + Self::new(s) + } +} + +impl From> for String { + #[inline] + fn from(s: InliningString) -> Self { + s.into_string() + } +} + +impl PartialEq> for InliningString { + #[inline] + fn eq(&self, other: &InliningString) -> bool { + **self == **other + } +} + +impl Eq for InliningString {} + +impl PartialOrd> for InliningString { + #[inline] + fn partial_cmp(&self, other: &InliningString) -> Option { + (**self).partial_cmp(&**other) + } +} + +impl Ord for InliningString { + #[inline] + fn cmp(&self, other: &Self) -> Ordering { + (**self).cmp(&**other) + } +} + +impl Hash for InliningString { + #[inline] + fn hash(&self, state: &mut H) { + (**self).hash(state); + } +} + +impl FromStr for InliningString { + type Err = Infallible; + + #[inline] + fn from_str(s: &str) -> Result { + Ok(Self::new(s)) + } +} + +impl fmt::Debug for InliningString { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Debug::fmt(&**self, f) + } +} + +impl fmt::Display for InliningString { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(&**self, f) + } +} + +#[cfg(feature = "serde")] +impl serde::Serialize for InliningString { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer + { + serde::Serialize::serialize(&**self, serializer) + } +} + +#[cfg(feature = "serde")] +impl<'de, const N: usize> serde::Deserialize<'de> for InliningString { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de> + { + serde::Deserialize::deserialize(deserializer) + .map(Self::new::<&'de str>) + } +} + +#[derive(Clone)] +enum Repr { + Inline(CappedString), + Boxed(Box), +} + +#[cfg(test)] +mod tests { + use std::borrow::Cow; + + use super::{InliningString, InliningString22}; + + #[test] + fn test_new() { + let test_strings = [ + "", + "Hello", + "Somethingfortheweekend", + "Dichlorodifluoromethane", + "こんにちは", + "❤️🧡💛💚💙💜" + ]; + + for s in test_strings { + let buf = s.to_owned(); + let borrowed = Cow::Borrowed(s); + let owned = Cow::<'static, str>::Owned(buf.clone()); + + assert_eq!(InliningString22::new(s).as_str(), s); + assert_eq!(InliningString22::new(buf).as_str(), s); + assert_eq!(InliningString22::new(borrowed).as_str(), s); + assert_eq!(InliningString22::new(owned).as_str(), s); + } + } + + #[test] + fn test_as_str_mut() { + let mut s1 = InliningString22::new("hello"); + s1.as_str_mut().make_ascii_uppercase(); + assert_eq!(s1.as_str(), "HELLO"); + + let mut s2 = InliningString22::new("the quick brown fox jumps over the lazy dog"); + s2.as_str_mut().make_ascii_uppercase(); + assert_eq!(s2.as_str(), "THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG"); + } + + #[test] + fn test_len() { + assert_eq!(InliningString22::new("").len(), 0); + assert_eq!(InliningString22::new("Hello").len(), 5); + assert_eq!(InliningString22::new("Somethingfortheweekend").len(), 22); + assert_eq!(InliningString22::new("Dichlorodifluoromethane").len(), 23); + assert_eq!(InliningString22::new("こんにちは").len(), 15); + assert_eq!(InliningString22::new("❤️🧡💛💚💙💜").len(), 26); + } + + #[test] + fn test_heap_allocated() { + assert!(!InliningString22::new("").heap_allocated()); + assert!(!InliningString22::new("Hello").heap_allocated()); + assert!(!InliningString22::new("Somethingfortheweekend").heap_allocated()); + assert!(!InliningString22::new("こんにちは").heap_allocated()); + + assert!(InliningString22::new("Dichlorodifluoromethane").heap_allocated()); + assert!(InliningString22::new("Squishedbuginsidethescreen").heap_allocated()); + assert!(InliningString22::new("❤️🧡💛💚💙💜").heap_allocated()); + } + + #[test] + fn test_zero_capacity() { + assert_eq!(InliningString::<0>::new("").as_str(), ""); + assert!(!InliningString::<0>::new("").heap_allocated()); + assert_eq!(InliningString::<0>::new("a").as_str(), "a"); + assert!(InliningString::<0>::new("a").heap_allocated()); + assert_eq!(InliningString::<0>::new("Hello").as_str(), "Hello"); + assert!(InliningString::<0>::new("Hello").heap_allocated()); + } +} diff --git a/src/strings/mod.rs b/src/strings/mod.rs index 8a3723a..cffe278 100644 --- a/src/strings/mod.rs +++ b/src/strings/mod.rs @@ -1,8 +1,8 @@ pub mod experimental; pub mod fixed; -pub mod inline; -pub mod shstring; +pub mod capped; +pub mod inlining; pub use fixed::{FixedString, Error as FixedStringError}; -pub use inline::{InlineString, Error as InlineStringError}; -pub use shstring::{ShString, ShString22}; +pub use capped::{CappedString, Error as CappedStringError}; +pub use inlining::{InliningString, InliningString22}; diff --git a/src/strings/shstring.rs b/src/strings/shstring.rs deleted file mode 100644 index d087555..0000000 --- a/src/strings/shstring.rs +++ /dev/null @@ -1,371 +0,0 @@ -use std::{ - borrow::{self, Cow}, - cmp::Ordering, - convert::Infallible, - fmt, - hash::{Hash, Hasher}, - ops, - str::FromStr, -}; - -use super::InlineString; - -/// A non-growable string where strings 22 bytes or shorter are stored on the stack and longer -/// strings are stored on the heap. -/// -/// 22 bytes is chosen because it is optimal for 64-bit architectures; the minimum possible size -/// of the data structure on 64-bit architectures which always keeps the data properly aligned is -/// 24 bytes (because, when heap-allocated, the data structure contains a 16-byte `Box<[u8]>` with -/// 8-byte alignment and a 1-byte discriminant, and the greatest multiple of 8 which is ≥17 is 24), -/// and the stack-allocated variant needs to store 2 extra bytes for the length and disciminant. -pub type ShString22 = ShString<22>; - -/// A non-growable string which may be allocated either on the stack or on the heap; strings `N` -/// bytes or shorter will be allocated on the stack, while strings longer than `N` bytes will be -/// allocated on the heap. Intended to be used when lots of small strings need to be stored, and -/// these strings do not need to grow. -/// -/// `N` must be less than or equal to `u8::MAX`. Exceeding this limit will cause a compile-time -/// error. Clearly it be better for `N` to be a `u8` rather than a `usize`, but this is -/// unfortunately not possible due to limitations of const generics. -#[derive(Clone)] -pub struct ShString(Repr); - -impl ShString { - #[inline] - #[must_use] - pub const fn empty() -> Self { - Self(Repr::Inline(InlineString::empty())) - } - - /// Creates a new `ShString` from the given string slice, putting it on the stack if possible - /// or creating a new heap allocation otherwise. - #[inline] - #[must_use] - pub fn new(s: S) -> Self - where - S: AsRef, - Box: From, - { - match InlineString::new(&s) { - Ok(stack_buf) => Self(Repr::Inline(stack_buf)), - Err(_) => Self(Repr::Boxed(Box::::from(s))), - } - } - - /// Returns a string slice for the underlying string data. - #[inline] - #[must_use] - pub fn as_str(&self) -> &str { - match self { - Self(Repr::Inline(buf)) => buf, - Self(Repr::Boxed(buf)) => buf, - } - } - - /// Returns a mutable string slice for the underlying string data. - #[inline] - #[must_use] - pub fn as_str_mut(&mut self) -> &mut str { - match self { - Self(Repr::Inline(buf)) => buf, - Self(Repr::Boxed(buf)) => buf, - } - } - - /// Consumes the `ShString` and converts it to a heap-allocated `String`. - #[inline] - #[must_use] - pub fn into_string(self) -> String { - match self { - Self(Repr::Inline(buf)) => buf.into_string(), - Self(Repr::Boxed(buf)) => buf.into_string(), - } - } - - /// Returns the length of the string in bytes. - /// - /// ``` - /// # use libshire::strings::ShString; - /// let s = ShString::<22>::new("こんにちは"); - /// assert_eq!(s.len(), 15); - /// ``` - #[inline] - #[must_use] - pub fn len(&self) -> usize { - match self { - Self(Repr::Inline(buf)) => buf.len(), - Self(Repr::Boxed(buf)) => buf.len(), - } - } - - /// Returns `true` if the string has length 0. - /// - /// ``` - /// # use libshire::strings::ShString; - /// let s1 = ShString::<22>::new(""); - /// assert!(s1.is_empty()); - /// - /// let s2 = ShString::<22>::new("Hello"); - /// assert!(!s2.is_empty()); - /// ``` - #[inline] - #[must_use] - pub fn is_empty(&self) -> bool { - match self { - Self(Repr::Inline(buf)) => buf.is_empty(), - Self(Repr::Boxed(buf)) => buf.is_empty(), - } - } - - /// Returns `true` if the string data is stored on the heap, and `false` otherwise. - /// - /// ``` - /// # use libshire::strings::ShString; - /// let s1 = ShString::<22>::new("This string's 22 bytes"); - /// assert!(!s1.heap_allocated()); - /// - /// let s2 = ShString::<22>::new("This string is 23 bytes"); - /// assert!(s2.heap_allocated()); - /// ``` - #[inline] - #[must_use] - pub fn heap_allocated(&self) -> bool { - match self { - Self(Repr::Inline(_)) => false, - Self(Repr::Boxed(_)) => true, - } - } -} - -impl Default for ShString { - #[inline] - fn default() -> Self { - Self::empty() - } -} - -impl ops::Deref for ShString { - type Target = str; - - #[inline] - fn deref(&self) -> &Self::Target { - self.as_str() - } -} - -impl ops::DerefMut for ShString { - #[inline] - fn deref_mut(&mut self) -> &mut Self::Target { - self.as_str_mut() - } -} - -impl AsRef for ShString { - #[inline] - fn as_ref(&self) -> &str { - self - } -} - -impl AsMut for ShString { - #[inline] - fn as_mut(&mut self) -> &mut str { - self - } -} - -impl borrow::Borrow for ShString { - #[inline] - fn borrow(&self) -> &str { - self - } -} - -impl borrow::BorrowMut for ShString { - #[inline] - fn borrow_mut(&mut self) -> &mut str { - self - } -} - -impl<'a, const N: usize> From<&'a str> for ShString { - #[inline] - fn from(s: &'a str) -> Self { - Self::new(s) - } -} - -impl From for ShString { - #[inline] - fn from(s: String) -> Self { - Self::new(s) - } -} - -impl<'a, const N: usize> From> for ShString { - #[inline] - fn from(s: Cow<'a, str>) -> Self { - Self::new(s) - } -} - -impl From> for String { - #[inline] - fn from(s: ShString) -> Self { - s.into_string() - } -} - -impl PartialEq> for ShString { - #[inline] - fn eq(&self, other: &ShString) -> bool { - **self == **other - } -} - -impl Eq for ShString {} - -impl PartialOrd> for ShString { - #[inline] - fn partial_cmp(&self, other: &ShString) -> Option { - (**self).partial_cmp(&**other) - } -} - -impl Ord for ShString { - #[inline] - fn cmp(&self, other: &Self) -> Ordering { - (**self).cmp(&**other) - } -} - -impl Hash for ShString { - #[inline] - fn hash(&self, state: &mut H) { - (**self).hash(state); - } -} - -impl FromStr for ShString { - type Err = Infallible; - - #[inline] - fn from_str(s: &str) -> Result { - Ok(Self::new(s)) - } -} - -impl fmt::Debug for ShString { - #[inline] - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Debug::fmt(&**self, f) - } -} - -impl fmt::Display for ShString { - #[inline] - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(&**self, f) - } -} - -#[cfg(feature = "serde")] -impl serde::Serialize for ShString { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer - { - serde::Serialize::serialize(&**self, serializer) - } -} - -#[cfg(feature = "serde")] -impl<'de, const N: usize> serde::Deserialize<'de> for ShString { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de> - { - serde::Deserialize::deserialize(deserializer) - .map(Self::new::<&'de str>) - } -} - -#[derive(Clone)] -enum Repr { - Inline(InlineString), - Boxed(Box), -} - -#[cfg(test)] -mod tests { - use std::borrow::Cow; - - use super::{ShString, ShString22}; - - #[test] - fn test_new() { - let test_strings = [ - "", - "Hello", - "Somethingfortheweekend", - "Dichlorodifluoromethane", - "こんにちは", - "❤️🧡💛💚💙💜" - ]; - - for s in test_strings { - let buf = s.to_owned(); - let borrowed = Cow::Borrowed(s); - let owned = Cow::<'static, str>::Owned(buf.clone()); - - assert_eq!(ShString22::new(s).as_str(), s); - assert_eq!(ShString22::new(buf).as_str(), s); - assert_eq!(ShString22::new(borrowed).as_str(), s); - assert_eq!(ShString22::new(owned).as_str(), s); - } - } - - #[test] - fn test_as_str_mut() { - let mut s1 = ShString22::new("hello"); - s1.as_str_mut().make_ascii_uppercase(); - assert_eq!(s1.as_str(), "HELLO"); - - let mut s2 = ShString22::new("the quick brown fox jumps over the lazy dog"); - s2.as_str_mut().make_ascii_uppercase(); - assert_eq!(s2.as_str(), "THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG"); - } - - #[test] - fn test_len() { - assert_eq!(ShString22::new("").len(), 0); - assert_eq!(ShString22::new("Hello").len(), 5); - assert_eq!(ShString22::new("Somethingfortheweekend").len(), 22); - assert_eq!(ShString22::new("Dichlorodifluoromethane").len(), 23); - assert_eq!(ShString22::new("こんにちは").len(), 15); - assert_eq!(ShString22::new("❤️🧡💛💚💙💜").len(), 26); - } - - #[test] - fn test_heap_allocated() { - assert!(!ShString22::new("").heap_allocated()); - assert!(!ShString22::new("Hello").heap_allocated()); - assert!(!ShString22::new("Somethingfortheweekend").heap_allocated()); - assert!(!ShString22::new("こんにちは").heap_allocated()); - - assert!(ShString22::new("Dichlorodifluoromethane").heap_allocated()); - assert!(ShString22::new("Squishedbuginsidethescreen").heap_allocated()); - assert!(ShString22::new("❤️🧡💛💚💙💜").heap_allocated()); - } - - #[test] - fn test_zero_capacity() { - assert_eq!(ShString::<0>::new("").as_str(), ""); - assert!(!ShString::<0>::new("").heap_allocated()); - assert_eq!(ShString::<0>::new("a").as_str(), "a"); - assert!(ShString::<0>::new("a").heap_allocated()); - assert_eq!(ShString::<0>::new("Hello").as_str(), "Hello"); - assert!(ShString::<0>::new("Hello").heap_allocated()); - } -}