diff --git a/src/backend.rs b/src/backend.rs new file mode 100644 index 0000000..3827082 --- /dev/null +++ b/src/backend.rs @@ -0,0 +1,97 @@ +#[cfg(feature = "arc")] +pub(crate) type DefaultStr = crate::backend::ArcStr; +#[cfg(not(feature = "arc"))] +pub(crate) type DefaultStr = crate::backend::BoxedStr; + +/// Fast allocations, O(n) clones +pub type BoxedStr = Box; +static_assertions::assert_eq_size!(DefaultStr, BoxedStr); + +/// Cross-thread, O(1) clones +pub type ArcStr = std::sync::Arc; +static_assertions::assert_eq_size!(DefaultStr, ArcStr); + +/// O(1) clones +pub type RcStr = std::rc::Rc; +static_assertions::assert_eq_size!(DefaultStr, RcStr); + +/// Abstract over different type of heap-allocated strings +pub trait HeapStr: std::fmt::Debug + Clone + private::Sealed { + fn from_str(other: &str) -> Self; + fn from_string(other: String) -> Self; + fn from_boxed_str(other: BoxedStr) -> Self; + fn as_str(&self) -> &str; +} + +impl HeapStr for BoxedStr { + #[inline] + fn from_str(other: &str) -> Self { + other.into() + } + + #[inline] + fn from_string(other: String) -> Self { + other.into_boxed_str() + } + + #[inline] + fn from_boxed_str(other: BoxedStr) -> Self { + other + } + + #[inline] + fn as_str(&self) -> &str { + self + } +} + +impl HeapStr for ArcStr { + #[inline] + fn from_str(other: &str) -> Self { + other.into() + } + + #[inline] + fn from_string(other: String) -> Self { + other.into_boxed_str().into() + } + + #[inline] + fn from_boxed_str(other: BoxedStr) -> Self { + other.into() + } + + #[inline] + fn as_str(&self) -> &str { + self + } +} + +impl HeapStr for RcStr { + #[inline] + fn from_str(other: &str) -> Self { + other.into() + } + + #[inline] + fn from_string(other: String) -> Self { + other.into_boxed_str().into() + } + + #[inline] + fn from_boxed_str(other: BoxedStr) -> Self { + other.into() + } + + #[inline] + fn as_str(&self) -> &str { + self + } +} + +pub(crate) mod private { + pub trait Sealed {} + impl Sealed for super::BoxedStr {} + impl Sealed for super::ArcStr {} + impl Sealed for super::RcStr {} +} diff --git a/src/lib.rs b/src/lib.rs index 8b6edf3..dc1ffd6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,21 @@ //! Key String: Optimized for map keys. //! +//! # Examples +//! +//! String creation +//! ```rust +//! // Explicit +//! let literal = kstring::KString::from_static("literal"); +//! // Implicit +//! let literal = kstring::KString::from("literal"); +//! +//! // Explicit +//! let inline = kstring::KString::try_inline("stack").unwrap(); +//! let inline = kstring::KString::from_ref("stack"); +//! +//! let formatted: kstring::KStringCow = format!("Hello {} and {}", literal, inline).into(); +//! ``` +//! //! # Background //! //! Considerations: @@ -12,8 +28,8 @@ //! //! Ramifications: //! - Inline small strings rather than going to the heap. -//! - Preserve `&'static str` across strings (`KString`), -//! references (`KStringRef`), and lifetime abstractions (`KStringCow`) to avoid +//! - Preserve `&'static str` across strings ([`KString`]), +//! references ([`KStringRef`]), and lifetime abstractions ([`KStringCow`]) to avoid //! allocating for struct field names. //! - Use `Box` rather than `String` to use less memory. //! @@ -30,6 +46,8 @@ mod string; mod string_cow; mod string_ref; +pub mod backend; + pub use stack::StackString; pub use string::*; pub use string_cow::*; @@ -45,11 +63,11 @@ mod test { ); println!( "Box: {}", - std::mem::size_of::() + std::mem::size_of::() ); println!( "Box>: {}", - std::mem::size_of::>() + std::mem::size_of::>() ); println!("str: {}", std::mem::size_of::<&'static str>()); println!( diff --git a/src/string.rs b/src/string.rs index 641e7d2..ed39d18 100644 --- a/src/string.rs +++ b/src/string.rs @@ -1,84 +1,84 @@ use std::{borrow::Cow, fmt}; use crate::stack::StackString; -use crate::KStringCow; +use crate::KStringCowBase; use crate::KStringRef; pub(crate) type StdString = std::string::String; -type BoxedStr = Box; -#[cfg(feature = "arc")] -pub(crate) type OwnedStr = std::sync::Arc; -#[cfg(not(feature = "arc"))] -pub(crate) type OwnedStr = Box; + +/// A UTF-8 encoded, immutable string. +pub type KString = KStringBase; /// A UTF-8 encoded, immutable string. #[derive(Clone)] #[repr(transparent)] -pub struct KString { - inner: KStringInner, +pub struct KStringBase { + inner: KStringInner, } -impl KString { - pub const EMPTY: Self = KString::from_static(""); +impl KStringBase { + pub const EMPTY: Self = KStringBase::from_static(""); - /// Create a new empty `KString`. + /// Create a new empty `KStringBase`. #[inline] #[must_use] pub fn new() -> Self { - Default::default() + Self::EMPTY } - /// Create an owned `KString`. + /// Create a reference to a `'static` data. #[inline] #[must_use] - pub fn from_boxed(other: BoxedStr) -> Self { + pub const fn from_static(other: &'static str) -> Self { Self { - inner: KStringInner::from_boxed(other), + inner: KStringInner::from_static(other), } } - /// Create an owned `KString`. + /// Create an inline string, if possible #[inline] #[must_use] - pub fn from_string(other: StdString) -> Self { - Self { - inner: KStringInner::from_string(other), - } + pub fn try_inline(other: &str) -> Option { + KStringInner::try_inline(other).map(|inner| Self { inner }) } +} - /// Create an owned `KString` optimally from a reference. +impl KStringBase { + /// Create an owned `KStringBase`. #[inline] #[must_use] - pub fn from_ref(other: &str) -> Self { + pub fn from_boxed(other: crate::backend::BoxedStr) -> Self { Self { - inner: KStringInner::from_ref(other), + inner: KStringInner::from_boxed(other), } } - /// Create a reference to a `'static` data. + /// Create an owned `KStringBase`. #[inline] #[must_use] - pub const fn from_static(other: &'static str) -> Self { + pub fn from_string(other: StdString) -> Self { Self { - inner: KStringInner::from_static(other), + inner: KStringInner::from_string(other), } } - /// Create an inline string, if possible + /// Create an owned `KStringBase` optimally from a reference. #[inline] #[must_use] - pub fn try_inline(other: &str) -> Option { - KStringInner::try_inline(other).map(|inner| Self { inner }) + pub fn from_ref(other: &str) -> Self { + Self { + inner: KStringInner::from_ref(other), + } } - /// Get a reference to the `KString`. + /// Get a reference to the `KStringBase`. #[inline] #[must_use] pub fn as_ref(&self) -> KStringRef<'_> { self.inner.as_ref() } - /// Extracts a string slice containing the entire `KString`. + /// Extracts a string slice containing the entire `KStringBase`. #[inline] #[must_use] pub fn as_str(&self) -> &str { @@ -95,7 +95,7 @@ impl KString { /// Convert to a mutable string type, cloning the data if necessary. #[inline] #[must_use] - pub fn into_boxed_str(self) -> BoxedStr { + pub fn into_boxed_str(self) -> crate::backend::BoxedStr { self.inner.into_boxed_str() } @@ -107,7 +107,7 @@ impl KString { } } -impl std::ops::Deref for KString { +impl std::ops::Deref for KStringBase { type Target = str; #[inline] @@ -116,186 +116,186 @@ impl std::ops::Deref for KString { } } -impl Eq for KString {} +impl Eq for KStringBase {} -impl<'s> PartialEq for KString { +impl<'s, B: crate::backend::HeapStr> PartialEq> for KStringBase { #[inline] - fn eq(&self, other: &KString) -> bool { + fn eq(&self, other: &Self) -> bool { PartialEq::eq(self.as_str(), other.as_str()) } } -impl<'s> PartialEq for KString { +impl<'s, B: crate::backend::HeapStr> PartialEq for KStringBase { #[inline] fn eq(&self, other: &str) -> bool { PartialEq::eq(self.as_str(), other) } } -impl<'s> PartialEq<&'s str> for KString { +impl<'s, B: crate::backend::HeapStr> PartialEq<&'s str> for KStringBase { #[inline] fn eq(&self, other: &&str) -> bool { PartialEq::eq(self.as_str(), *other) } } -impl<'s> PartialEq for KString { +impl<'s, B: crate::backend::HeapStr> PartialEq for KStringBase { #[inline] fn eq(&self, other: &StdString) -> bool { PartialEq::eq(self.as_str(), other.as_str()) } } -impl Ord for KString { +impl Ord for KStringBase { #[inline] fn cmp(&self, other: &Self) -> std::cmp::Ordering { self.as_str().cmp(other.as_str()) } } -impl PartialOrd for KString { +impl PartialOrd for KStringBase { #[inline] fn partial_cmp(&self, other: &Self) -> Option { self.as_str().partial_cmp(other.as_str()) } } -impl std::hash::Hash for KString { +impl std::hash::Hash for KStringBase { #[inline] fn hash(&self, state: &mut H) { self.as_str().hash(state); } } -impl fmt::Debug for KString { +impl fmt::Debug for KStringBase { #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.as_str().fmt(f) } } -impl fmt::Display for KString { +impl fmt::Display for KStringBase { #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(self.as_str(), f) } } -impl AsRef for KString { +impl AsRef for KStringBase { #[inline] fn as_ref(&self) -> &str { self.as_str() } } -impl AsRef<[u8]> for KString { +impl AsRef<[u8]> for KStringBase { #[inline] fn as_ref(&self) -> &[u8] { self.as_bytes() } } -impl AsRef for KString { +impl AsRef for KStringBase { #[inline] fn as_ref(&self) -> &std::ffi::OsStr { (&**self).as_ref() } } -impl AsRef for KString { +impl AsRef for KStringBase { #[inline] fn as_ref(&self) -> &std::path::Path { std::path::Path::new(self) } } -impl std::borrow::Borrow for KString { +impl std::borrow::Borrow for KStringBase { #[inline] fn borrow(&self) -> &str { self.as_str() } } -impl Default for KString { +impl Default for KStringBase { #[inline] fn default() -> Self { - Self::from_static("") + Self::new() } } -impl<'s> From> for KString { +impl<'s, B: crate::backend::HeapStr> From> for KStringBase { #[inline] fn from(other: KStringRef<'s>) -> Self { other.to_owned() } } -impl<'s> From<&'s KStringRef<'s>> for KString { +impl<'s, B: crate::backend::HeapStr> From<&'s KStringRef<'s>> for KStringBase { #[inline] fn from(other: &'s KStringRef<'s>) -> Self { other.to_owned() } } -impl<'s> From> for KString { +impl<'s, B: crate::backend::HeapStr> From> for KStringBase { #[inline] - fn from(other: KStringCow<'s>) -> Self { + fn from(other: KStringCowBase<'s, B>) -> Self { other.into_owned() } } -impl<'s> From<&'s KStringCow<'s>> for KString { +impl<'s, B: crate::backend::HeapStr> From<&'s KStringCowBase<'s, B>> for KStringBase { #[inline] - fn from(other: &'s KStringCow<'s>) -> Self { + fn from(other: &'s KStringCowBase<'s, B>) -> Self { other.clone().into_owned() } } -impl From for KString { +impl From for KStringBase { #[inline] fn from(other: StdString) -> Self { Self::from_string(other) } } -impl<'s> From<&'s StdString> for KString { +impl<'s, B: crate::backend::HeapStr> From<&'s StdString> for KStringBase { #[inline] fn from(other: &'s StdString) -> Self { Self::from_ref(other) } } -impl From for KString { +impl From for KStringBase { #[inline] - fn from(other: BoxedStr) -> Self { + fn from(other: crate::backend::BoxedStr) -> Self { Self::from_boxed(other) } } -impl<'s> From<&'s BoxedStr> for KString { +impl<'s, B: crate::backend::HeapStr> From<&'s crate::backend::BoxedStr> for KStringBase { #[inline] - fn from(other: &'s BoxedStr) -> Self { + fn from(other: &'s crate::backend::BoxedStr) -> Self { Self::from_ref(other) } } -impl From<&'static str> for KString { +impl From<&'static str> for KStringBase { #[inline] fn from(other: &'static str) -> Self { Self::from_static(other) } } -impl std::str::FromStr for KString { +impl std::str::FromStr for KStringBase { type Err = std::convert::Infallible; #[inline] fn from_str(s: &str) -> Result { - Ok(KString::from_ref(s)) + Ok(Self::from_ref(s)) } } #[cfg(feature = "serde")] -impl serde::Serialize for KString { +impl serde::Serialize for KStringBase { #[inline] fn serialize(&self, serializer: S) -> Result where @@ -306,21 +306,21 @@ impl serde::Serialize for KString { } #[cfg(feature = "serde")] -impl<'de> serde::Deserialize<'de> for KString { +impl<'de, B: crate::backend::HeapStr> serde::Deserialize<'de> for KStringBase { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { - deserializer.deserialize_string(StringVisitor) + deserializer.deserialize_string(StringVisitor(std::marker::PhantomData)) } } #[cfg(feature = "serde")] -struct StringVisitor; +struct StringVisitor(std::marker::PhantomData); #[cfg(feature = "serde")] -impl<'de> serde::de::Visitor<'de> for StringVisitor { - type Value = KString; +impl<'de, B: crate::backend::HeapStr> serde::de::Visitor<'de> for StringVisitor { + type Value = KStringBase; fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter.write_str("a string") @@ -330,14 +330,14 @@ impl<'de> serde::de::Visitor<'de> for StringVisitor { where E: serde::de::Error, { - Ok(KString::from_ref(v)) + Ok(Self::Value::from_ref(v)) } fn visit_string(self, v: String) -> Result where E: serde::de::Error, { - Ok(KString::from_string(v)) + Ok(Self::Value::from_string(v)) } fn visit_bytes(self, v: &[u8]) -> Result @@ -345,7 +345,7 @@ impl<'de> serde::de::Visitor<'de> for StringVisitor { E: serde::de::Error, { match std::str::from_utf8(v) { - Ok(s) => Ok(KString::from_ref(s)), + Ok(s) => Ok(Self::Value::from_ref(s)), Err(_) => Err(serde::de::Error::invalid_value( serde::de::Unexpected::Bytes(v), &self, @@ -358,7 +358,7 @@ impl<'de> serde::de::Visitor<'de> for StringVisitor { E: serde::de::Error, { match String::from_utf8(v) { - Ok(s) => Ok(KString::from_string(s)), + Ok(s) => Ok(Self::Value::from_string(s)), Err(e) => Err(serde::de::Error::invalid_value( serde::de::Unexpected::Bytes(&e.into_bytes()), &self, @@ -373,17 +373,30 @@ use inner::KStringInner; mod inner { use super::*; - pub(super) enum KStringInner { + pub(super) enum KStringInner { Singleton(&'static str), Inline(StackString), - Owned(OwnedStr), + Owned(B), } - impl KStringInner { + impl KStringInner { + /// Create a reference to a `'static` data. #[inline] - pub(super) fn from_boxed(other: BoxedStr) -> Self { + pub const fn from_static(other: &'static str) -> Self { + Self::Singleton(other) + } + + #[inline] + pub fn try_inline(other: &str) -> Option { + StackString::try_new(other).map(Self::Inline) + } + } + + impl KStringInner { + #[inline] + pub(super) fn from_boxed(other: crate::backend::BoxedStr) -> Self { #[allow(clippy::useless_conversion)] - Self::Owned(OwnedStr::from(other)) + Self::Owned(B::from_boxed_str(other)) } #[inline] @@ -392,8 +405,7 @@ mod inner { let inline = { StackString::new(other.as_str()) }; Self::Inline(inline) } else { - #[allow(clippy::useless_conversion)] - Self::Owned(OwnedStr::from(other.into_boxed_str())) + Self::from_boxed(other.into_boxed_str()) } } @@ -403,28 +415,16 @@ mod inner { let inline = { StackString::new(other) }; Self::Inline(inline) } else { - #[allow(clippy::useless_conversion)] - Self::Owned(OwnedStr::from(other)) + Self::Owned(B::from_str(other)) } } - /// Create a reference to a `'static` data. - #[inline] - pub const fn from_static(other: &'static str) -> Self { - Self::Singleton(other) - } - - #[inline] - pub fn try_inline(other: &str) -> Option { - StackString::try_new(other).map(Self::Inline) - } - #[inline] pub(super) fn as_ref(&self) -> KStringRef<'_> { match self { Self::Singleton(s) => KStringRef::from_static(s), Self::Inline(s) => KStringRef::from_ref(s.as_str()), - Self::Owned(s) => KStringRef::from_ref(s), + Self::Owned(s) => KStringRef::from_ref(s.as_str()), } } @@ -433,16 +433,16 @@ mod inner { match self { Self::Singleton(s) => s, Self::Inline(s) => s.as_str(), - Self::Owned(s) => s, + Self::Owned(s) => s.as_str(), } } #[inline] - pub(super) fn into_boxed_str(self) -> BoxedStr { + pub(super) fn into_boxed_str(self) -> crate::backend::BoxedStr { match self { - Self::Singleton(s) => BoxedStr::from(s), - Self::Inline(s) => BoxedStr::from(s.as_str()), - Self::Owned(s) => BoxedStr::from(s.as_ref()), + Self::Singleton(s) => crate::backend::BoxedStr::from(s), + Self::Inline(s) => crate::backend::BoxedStr::from(s.as_str()), + Self::Owned(s) => crate::backend::BoxedStr::from(s.as_str()), } } @@ -452,7 +452,7 @@ mod inner { match self { Self::Singleton(s) => Cow::Borrowed(s), Self::Inline(s) => Cow::Owned(s.as_str().into()), - Self::Owned(s) => Cow::Owned(s.as_ref().into()), + Self::Owned(s) => Cow::Owned(s.as_str().into()), } } } @@ -465,7 +465,7 @@ mod inner { // // My only guess is that the `clone()` calls we delegate to are just that much bigger than // `as_str()` that, when combined with a jump table, is blowing the icache, slowing things down. - impl Clone for KStringInner { + impl Clone for KStringInner { fn clone(&self) -> Self { match self { Self::Singleton(s) => Self::Singleton(s), @@ -489,7 +489,7 @@ mod inner { // discriminant. The question is whether faster len=1-16 "allocations" outweighs going to the heap // for len=17-22. #[allow(unused)] - const ALIGNED_CAPACITY: usize = std::mem::size_of::() - LEN_SIZE; + const ALIGNED_CAPACITY: usize = std::mem::size_of::() - LEN_SIZE; #[cfg(feature = "max_inline")] const CAPACITY: usize = MAX_CAPACITY; @@ -501,18 +501,43 @@ mod inner { mod inner { use super::*; - pub(super) union KStringInner { + pub(super) union KStringInner { tag: TagVariant, singleton: SingletonVariant, - owned: std::mem::ManuallyDrop, + owned: std::mem::ManuallyDrop>, inline: InlineVariant, } - impl KStringInner { + impl KStringInner { + /// Create a reference to a `'static` data. + #[inline] + pub const fn from_static(other: &'static str) -> Self { + Self { + singleton: SingletonVariant::new(other), + } + } + + #[inline] + pub fn try_inline(other: &str) -> Option { + StackString::try_new(other).map(|inline| Self { + inline: InlineVariant::new(inline), + }) + } + + #[inline] + const fn tag(&self) -> Tag { + unsafe { + // SAFETY: `tag` is in the same spot in each variant + self.tag.tag + } + } + } + + impl KStringInner { #[inline] - pub(super) fn from_boxed(other: BoxedStr) -> Self { + pub(super) fn from_boxed(other: crate::backend::BoxedStr) -> Self { #[allow(clippy::useless_conversion)] - let payload = OwnedStr::from(other); + let payload = B::from_boxed_str(other); Self { owned: std::mem::ManuallyDrop::new(OwnedVariant::new(payload)), } @@ -545,28 +570,13 @@ mod inner { } } else { #[allow(clippy::useless_conversion)] - let payload = OwnedStr::from(other); + let payload = B::from_str(other); Self { owned: std::mem::ManuallyDrop::new(OwnedVariant::new(payload)), } } } - /// Create a reference to a `'static` data. - #[inline] - pub const fn from_static(other: &'static str) -> Self { - Self { - singleton: SingletonVariant::new(other), - } - } - - #[inline] - pub fn try_inline(other: &str) -> Option { - StackString::try_new(other).map(|inline| Self { - inline: InlineVariant::new(inline), - }) - } - #[inline] pub(super) fn as_ref(&self) -> KStringRef<'_> { let tag = self.tag(); @@ -575,7 +585,7 @@ mod inner { if tag.is_singleton() { KStringRef::from_static(self.singleton.payload) } else if tag.is_owned() { - KStringRef::from_ref(self.owned.payload.as_ref()) + KStringRef::from_ref(self.owned.payload.as_str()) } else { debug_assert!(tag.is_inline()); KStringRef::from_ref(self.inline.payload.as_str()) @@ -591,7 +601,7 @@ mod inner { if tag.is_singleton() { self.singleton.payload } else if tag.is_owned() { - self.owned.payload.as_ref() + self.owned.payload.as_str() } else { debug_assert!(tag.is_inline()); self.inline.payload.as_str() @@ -600,17 +610,17 @@ mod inner { } #[inline] - pub(super) fn into_boxed_str(self) -> BoxedStr { + pub(super) fn into_boxed_str(self) -> crate::backend::BoxedStr { let tag = self.tag(); unsafe { // SAFETY: `tag` ensures access to correct variant if tag.is_singleton() { - BoxedStr::from(self.singleton.payload) + crate::backend::BoxedStr::from(self.singleton.payload) } else if tag.is_owned() { - BoxedStr::from(self.owned.payload.as_ref()) + crate::backend::BoxedStr::from(self.owned.payload.as_str()) } else { debug_assert!(tag.is_inline()); - BoxedStr::from(self.inline.payload.as_ref()) + crate::backend::BoxedStr::from(self.inline.payload.as_ref()) } } } @@ -624,21 +634,13 @@ mod inner { if tag.is_singleton() { Cow::Borrowed(self.singleton.payload) } else if tag.is_owned() { - Cow::Owned(self.owned.payload.as_ref().into()) + Cow::Owned(self.owned.payload.as_str().into()) } else { debug_assert!(tag.is_inline()); Cow::Owned(self.inline.payload.as_str().into()) } } } - - #[inline] - fn tag(&self) -> Tag { - unsafe { - // SAFETY: `tag` is in the same spot in each variant - self.tag.tag - } - } } // Explicit to avoid inlining which cuts clone times in half. @@ -649,7 +651,7 @@ mod inner { // // My only guess is that the `clone()` calls we delegate to are just that much bigger than // `as_str()` that, when combined with a jump table, is blowing the icache, slowing things down. - impl Clone for KStringInner { + impl Clone for KStringInner { fn clone(&self) -> Self { let tag = self.tag(); if tag.is_owned() { @@ -671,7 +673,7 @@ mod inner { } } - impl Drop for KStringInner { + impl Drop for KStringInner { fn drop(&mut self) { let tag = self.tag(); if tag.is_owned() { @@ -690,7 +692,7 @@ mod inner { const TAG_SIZE: usize = std::mem::size_of::(); #[allow(unused)] - const PAYLOAD_SIZE: usize = std::mem::size_of::(); + const PAYLOAD_SIZE: usize = std::mem::size_of::(); type Payload = Padding; #[allow(unused)] @@ -753,17 +755,17 @@ mod inner { #[derive(Clone)] #[repr(C)] - struct OwnedVariant { - payload: OwnedStr, + struct OwnedVariant { + payload: B, pad: Padding, tag: Tag, } - static_assertions::assert_eq_size!(Payload, std::mem::ManuallyDrop); - static_assertions::assert_eq_size!(Target, OwnedVariant); + static_assertions::assert_eq_size!(Payload, crate::backend::DefaultStr); + static_assertions::assert_eq_size!(Target, OwnedVariant); - impl OwnedVariant { + impl OwnedVariant { #[inline] - const fn new(payload: OwnedStr) -> Self { + const fn new(payload: B) -> Self { Self { payload, pad: Padding::new(), @@ -772,7 +774,7 @@ mod inner { } } - impl std::fmt::Debug for OwnedVariant { + impl std::fmt::Debug for OwnedVariant { #[inline] fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.payload.fmt(f) diff --git a/src/string_cow.rs b/src/string_cow.rs index 7919aa4..6a1b4b8 100644 --- a/src/string_cow.rs +++ b/src/string_cow.rs @@ -1,70 +1,75 @@ use std::{borrow::Cow, fmt}; -use crate::KString; +use crate::KStringBase; use crate::KStringRef; use crate::KStringRefInner; type StdString = std::string::String; type BoxedStr = Box; +/// A reference to a UTF-8 encoded, immutable string. +pub type KStringCow<'s> = KStringCowBase<'s, crate::backend::DefaultStr>; + /// A reference to a UTF-8 encoded, immutable string. #[derive(Clone)] #[repr(transparent)] -pub struct KStringCow<'s> { - pub(crate) inner: KStringCowInner<'s>, +pub struct KStringCowBase<'s, B = crate::backend::DefaultStr> { + pub(crate) inner: KStringCowInner<'s, B>, } -#[derive(Clone, Debug)] -pub(crate) enum KStringCowInner<'s> { +#[derive(Clone)] +pub(crate) enum KStringCowInner<'s, B> { Borrowed(&'s str), - Owned(KString), + Owned(KStringBase), } -impl<'s> KStringCow<'s> { - /// Create a new empty `KStringCow`. +impl<'s, B> KStringCowBase<'s, B> { + /// Create a new empty `KStringCowBase`. #[inline] #[must_use] - pub fn new() -> Self { - Default::default() + pub const fn new() -> Self { + Self::from_static("") } - /// Create an owned `KStringCow`. + /// Create a reference to a `'static` data. #[inline] #[must_use] - pub fn from_boxed(other: BoxedStr) -> Self { + pub const fn from_static(other: &'static str) -> Self { Self { - inner: KStringCowInner::Owned(KString::from_boxed(other)), + inner: KStringCowInner::Owned(KStringBase::from_static(other)), } } +} - /// Create an owned `KStringCow`. +impl<'s, B: crate::backend::HeapStr> KStringCowBase<'s, B> { + /// Create an owned `KStringCowBase`. #[inline] #[must_use] - pub fn from_string(other: StdString) -> Self { + pub fn from_boxed(other: BoxedStr) -> Self { Self { - inner: KStringCowInner::Owned(KString::from_string(other)), + inner: KStringCowInner::Owned(KStringBase::from_boxed(other)), } } - /// Create a reference to a borrowed data. + /// Create an owned `KStringCowBase`. #[inline] #[must_use] - pub fn from_ref(other: &'s str) -> Self { + pub fn from_string(other: StdString) -> Self { Self { - inner: KStringCowInner::Borrowed(other), + inner: KStringCowInner::Owned(KStringBase::from_string(other)), } } - /// Create a reference to a `'static` data. + /// Create a reference to a borrowed data. #[inline] #[must_use] - pub fn from_static(other: &'static str) -> Self { + pub fn from_ref(other: &'s str) -> Self { Self { - inner: KStringCowInner::Owned(KString::from_static(other)), + inner: KStringCowInner::Borrowed(other), } } - /// Get a reference to the `KString`. + /// Get a reference to the `KStringBase`. #[inline] #[must_use] pub fn as_ref(&self) -> KStringRef<'_> { @@ -74,11 +79,11 @@ impl<'s> KStringCow<'s> { /// Clone the data into an owned-type. #[inline] #[must_use] - pub fn into_owned(self) -> KString { + pub fn into_owned(self) -> KStringBase { self.inner.into_owned() } - /// Extracts a string slice containing the entire `KStringCow`. + /// Extracts a string slice containing the entire `KStringCowBase`. #[inline] #[must_use] pub fn as_str(&self) -> &str { @@ -107,7 +112,7 @@ impl<'s> KStringCow<'s> { } } -impl<'s> KStringCowInner<'s> { +impl<'s, B: crate::backend::HeapStr> KStringCowInner<'s, B> { #[inline] fn as_ref(&self) -> KStringRef<'_> { match self { @@ -117,9 +122,9 @@ impl<'s> KStringCowInner<'s> { } #[inline] - fn into_owned(self) -> KString { + fn into_owned(self) -> KStringBase { match self { - Self::Borrowed(s) => KString::from_ref(s), + Self::Borrowed(s) => KStringBase::from_ref(s), Self::Owned(s) => s, } } @@ -150,7 +155,7 @@ impl<'s> KStringCowInner<'s> { } } -impl<'s> std::ops::Deref for KStringCow<'s> { +impl<'s, B: crate::backend::HeapStr> std::ops::Deref for KStringCowBase<'s, B> { type Target = str; #[inline] @@ -159,130 +164,130 @@ impl<'s> std::ops::Deref for KStringCow<'s> { } } -impl<'s> Eq for KStringCow<'s> {} +impl<'s, B: crate::backend::HeapStr> Eq for KStringCowBase<'s, B> {} -impl<'s> PartialEq> for KStringCow<'s> { +impl<'s, B: crate::backend::HeapStr> PartialEq> for KStringCowBase<'s, B> { #[inline] - fn eq(&self, other: &KStringCow<'s>) -> bool { + fn eq(&self, other: &KStringCowBase<'s, B>) -> bool { PartialEq::eq(self.as_str(), other.as_str()) } } -impl<'s> PartialEq for KStringCow<'s> { +impl<'s, B: crate::backend::HeapStr> PartialEq for KStringCowBase<'s, B> { #[inline] fn eq(&self, other: &str) -> bool { PartialEq::eq(self.as_str(), other) } } -impl<'s> PartialEq<&'s str> for KStringCow<'s> { +impl<'s, B: crate::backend::HeapStr> PartialEq<&'s str> for KStringCowBase<'s, B> { #[inline] fn eq(&self, other: &&str) -> bool { PartialEq::eq(self.as_str(), *other) } } -impl<'s> PartialEq for KStringCow<'s> { +impl<'s, B: crate::backend::HeapStr> PartialEq for KStringCowBase<'s, B> { #[inline] fn eq(&self, other: &StdString) -> bool { PartialEq::eq(self.as_str(), other.as_str()) } } -impl<'s> Ord for KStringCow<'s> { +impl<'s, B: crate::backend::HeapStr> Ord for KStringCowBase<'s, B> { #[inline] fn cmp(&self, other: &Self) -> std::cmp::Ordering { self.as_str().cmp(other.as_str()) } } -impl<'s> PartialOrd for KStringCow<'s> { +impl<'s, B: crate::backend::HeapStr> PartialOrd for KStringCowBase<'s, B> { #[inline] fn partial_cmp(&self, other: &Self) -> Option { self.as_str().partial_cmp(other.as_str()) } } -impl<'s> std::hash::Hash for KStringCow<'s> { +impl<'s, B: crate::backend::HeapStr> std::hash::Hash for KStringCowBase<'s, B> { #[inline] fn hash(&self, state: &mut H) { self.as_str().hash(state); } } -impl<'s> fmt::Debug for KStringCow<'s> { +impl<'s, B: crate::backend::HeapStr> fmt::Debug for KStringCowBase<'s, B> { #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(&self.inner, f) + self.as_str().fmt(f) } } -impl<'s> fmt::Display for KStringCow<'s> { +impl<'s, B: crate::backend::HeapStr> fmt::Display for KStringCowBase<'s, B> { #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(self.as_str(), f) } } -impl<'s> AsRef for KStringCow<'s> { +impl<'s, B: crate::backend::HeapStr> AsRef for KStringCowBase<'s, B> { #[inline] fn as_ref(&self) -> &str { self.as_str() } } -impl<'s> AsRef<[u8]> for KStringCow<'s> { +impl<'s, B: crate::backend::HeapStr> AsRef<[u8]> for KStringCowBase<'s, B> { #[inline] fn as_ref(&self) -> &[u8] { self.as_bytes() } } -impl<'s> AsRef for KStringCow<'s> { +impl<'s, B: crate::backend::HeapStr> AsRef for KStringCowBase<'s, B> { #[inline] fn as_ref(&self) -> &std::ffi::OsStr { (&**self).as_ref() } } -impl<'s> AsRef for KStringCow<'s> { +impl<'s, B: crate::backend::HeapStr> AsRef for KStringCowBase<'s, B> { #[inline] fn as_ref(&self) -> &std::path::Path { std::path::Path::new(self) } } -impl<'s> std::borrow::Borrow for KStringCow<'s> { +impl<'s, B: crate::backend::HeapStr> std::borrow::Borrow for KStringCowBase<'s, B> { #[inline] fn borrow(&self) -> &str { self.as_str() } } -impl<'s> Default for KStringCow<'s> { +impl<'s, B> Default for KStringCowBase<'s, B> { #[inline] fn default() -> Self { - Self::from_static("") + Self::new() } } -impl<'s> From for KStringCow<'s> { +impl<'s, B: crate::backend::HeapStr> From> for KStringCowBase<'s, B> { #[inline] - fn from(other: KString) -> Self { + fn from(other: KStringBase) -> Self { let inner = KStringCowInner::Owned(other); Self { inner } } } -impl<'s> From<&'s KString> for KStringCow<'s> { +impl<'s, B: crate::backend::HeapStr> From<&'s KStringBase> for KStringCowBase<'s, B> { #[inline] - fn from(other: &'s KString) -> Self { + fn from(other: &'s KStringBase) -> Self { let other = other.as_ref(); other.into() } } -impl<'s> From> for KStringCow<'s> { +impl<'s, B: crate::backend::HeapStr> From> for KStringCowBase<'s, B> { #[inline] fn from(other: KStringRef<'s>) -> Self { match other.inner { @@ -292,7 +297,7 @@ impl<'s> From> for KStringCow<'s> { } } -impl<'s> From<&'s KStringRef<'s>> for KStringCow<'s> { +impl<'s, B: crate::backend::HeapStr> From<&'s KStringRef<'s>> for KStringCowBase<'s, B> { #[inline] fn from(other: &'s KStringRef<'s>) -> Self { match other.inner { @@ -302,21 +307,21 @@ impl<'s> From<&'s KStringRef<'s>> for KStringCow<'s> { } } -impl<'s> From for KStringCow<'s> { +impl<'s, B: crate::backend::HeapStr> From for KStringCowBase<'s, B> { #[inline] fn from(other: StdString) -> Self { Self::from_string(other) } } -impl<'s> From<&'s StdString> for KStringCow<'s> { +impl<'s, B: crate::backend::HeapStr> From<&'s StdString> for KStringCowBase<'s, B> { #[inline] fn from(other: &'s StdString) -> Self { Self::from_ref(other.as_str()) } } -impl<'s> From for KStringCow<'s> { +impl<'s, B: crate::backend::HeapStr> From for KStringCowBase<'s, B> { #[inline] fn from(other: BoxedStr) -> Self { // Since the memory is already allocated, don't bother moving it into a FixedString @@ -324,21 +329,21 @@ impl<'s> From for KStringCow<'s> { } } -impl<'s> From<&'s BoxedStr> for KStringCow<'s> { +impl<'s, B: crate::backend::HeapStr> From<&'s BoxedStr> for KStringCowBase<'s, B> { #[inline] fn from(other: &'s BoxedStr) -> Self { Self::from_ref(other) } } -impl<'s> From<&'s str> for KStringCow<'s> { +impl<'s, B: crate::backend::HeapStr> From<&'s str> for KStringCowBase<'s, B> { #[inline] fn from(other: &'s str) -> Self { Self::from_ref(other) } } -impl std::str::FromStr for KStringCow<'_> { +impl std::str::FromStr for KStringCowBase<'_, B> { type Err = std::convert::Infallible; #[inline] fn from_str(s: &str) -> Result { @@ -347,7 +352,7 @@ impl std::str::FromStr for KStringCow<'_> { } #[cfg(feature = "serde")] -impl<'s> serde::Serialize for KStringCow<'s> { +impl<'s, B: crate::backend::HeapStr> serde::Serialize for KStringCowBase<'s, B> { #[inline] fn serialize(&self, serializer: S) -> Result where @@ -358,12 +363,12 @@ impl<'s> serde::Serialize for KStringCow<'s> { } #[cfg(feature = "serde")] -impl<'de, 's> serde::Deserialize<'de> for KStringCow<'s> { +impl<'de, 's, B: crate::backend::HeapStr> serde::Deserialize<'de> for KStringCowBase<'s, B> { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { - KString::deserialize(deserializer).map(|s| s.into()) + KStringBase::deserialize(deserializer).map(|s| s.into()) } } diff --git a/src/string_ref.rs b/src/string_ref.rs index 4775f64..a79b9d4 100644 --- a/src/string_ref.rs +++ b/src/string_ref.rs @@ -1,7 +1,7 @@ use std::fmt; -use crate::KString; -use crate::KStringCow; +use crate::KStringBase; +use crate::KStringCowBase; type StdString = std::string::String; type BoxedStr = Box; @@ -20,28 +20,28 @@ pub(crate) enum KStringRefInner<'s> { } impl<'s> KStringRef<'s> { - /// Create a new empty `KString`. + /// Create a new empty `KStringBase`. #[inline] #[must_use] - pub fn new() -> Self { - Default::default() + pub const fn new() -> Self { + Self::from_static("") } - /// Create a reference to a borrowed data. + /// Create a reference to a `'static` data. #[inline] #[must_use] - pub fn from_ref(other: &'s str) -> Self { + pub const fn from_static(other: &'static str) -> Self { Self { - inner: KStringRefInner::Borrowed(other), + inner: KStringRefInner::Singleton(other), } } - /// Create a reference to a `'static` data. + /// Create a reference to a borrowed data. #[inline] #[must_use] - pub fn from_static(other: &'static str) -> Self { + pub fn from_ref(other: &'s str) -> Self { Self { - inner: KStringRefInner::Singleton(other), + inner: KStringRefInner::Borrowed(other), } } @@ -49,7 +49,7 @@ impl<'s> KStringRef<'s> { #[inline] #[must_use] #[allow(clippy::wrong_self_convention)] - pub fn to_owned(&self) -> KString { + pub fn to_owned(&self) -> KStringBase { self.inner.to_owned() } @@ -71,10 +71,10 @@ impl<'s> KStringRef<'s> { impl<'s> KStringRefInner<'s> { #[inline] #[allow(clippy::wrong_self_convention)] - fn to_owned(&self) -> KString { + fn to_owned(&self) -> KStringBase { match self { - Self::Borrowed(s) => KString::from_ref(s), - Self::Singleton(s) => KString::from_static(s), + Self::Borrowed(s) => KStringBase::from_ref(s), + Self::Singleton(s) => KStringBase::from_static(s), } } @@ -204,20 +204,20 @@ impl<'s> std::borrow::Borrow for KStringRef<'s> { impl<'s> Default for KStringRef<'s> { #[inline] fn default() -> Self { - Self::from_static("") + Self::new() } } -impl<'s> From<&'s KString> for KStringRef<'s> { +impl<'s, B: crate::backend::HeapStr> From<&'s KStringBase> for KStringRef<'s> { #[inline] - fn from(other: &'s KString) -> Self { + fn from(other: &'s KStringBase) -> Self { other.as_ref() } } -impl<'s> From<&'s KStringCow<'s>> for KStringRef<'s> { +impl<'s, B: crate::backend::HeapStr> From<&'s KStringCowBase<'s, B>> for KStringRef<'s> { #[inline] - fn from(other: &'s KStringCow<'s>) -> Self { + fn from(other: &'s KStringCowBase<'s, B>) -> Self { other.as_ref() } }