kstring/
string.rs

1use std::{borrow::Cow, fmt};
2
3use crate::inline::*;
4use crate::KStringCow;
5use crate::KStringRef;
6
7pub(crate) type StdString = std::string::String;
8type BoxedStr = Box<str>;
9#[cfg(feature = "arc")]
10pub(crate) type OwnedStr = std::sync::Arc<str>;
11#[cfg(not(feature = "arc"))]
12pub(crate) type OwnedStr = Box<str>;
13
14/// A UTF-8 encoded, immutable string.
15#[derive(Clone)]
16#[repr(transparent)]
17pub struct KString {
18    pub(crate) inner: KStringInner,
19}
20
21#[derive(Debug)]
22pub(crate) enum KStringInner {
23    Singleton(&'static str),
24    Inline(InlineString),
25    Owned(OwnedStr),
26}
27
28impl KString {
29    /// Create a new empty `KString`.
30    #[inline]
31    pub fn new() -> Self {
32        Default::default()
33    }
34
35    /// Create an owned `KString`.
36    #[inline]
37    pub fn from_boxed(other: BoxedStr) -> Self {
38        Self {
39            inner: KStringInner::Owned(OwnedStr::from(other)),
40        }
41    }
42
43    /// Create an owned `KString`.
44    #[inline]
45    pub fn from_string(other: StdString) -> Self {
46        let inner = if (0..=CAPACITY).contains(&other.len()) {
47            KStringInner::Inline(InlineString::new(other.as_str()))
48        } else {
49            KStringInner::Owned(OwnedStr::from(other.into_boxed_str()))
50        };
51        Self { inner }
52    }
53
54    /// Create an owned `KString` optimally from a reference.
55    #[inline]
56    pub fn from_ref(other: &str) -> Self {
57        let inner = if (0..=CAPACITY).contains(&other.len()) {
58            KStringInner::Inline(InlineString::new(other))
59        } else {
60            KStringInner::Owned(OwnedStr::from(other))
61        };
62        Self { inner }
63    }
64
65    /// Create a reference to a `'static` data.
66    #[inline]
67    pub fn from_static(other: &'static str) -> Self {
68        Self {
69            inner: KStringInner::Singleton(other),
70        }
71    }
72
73    /// Get a reference to the `KString`.
74    #[inline]
75    pub fn as_ref(&self) -> KStringRef<'_> {
76        self.inner.as_ref()
77    }
78
79    /// Extracts a string slice containing the entire `KString`.
80    #[inline]
81    pub fn as_str(&self) -> &str {
82        self.inner.as_str()
83    }
84
85    /// Convert to a mutable string type, cloning the data if necessary.
86    #[inline]
87    pub fn into_string(self) -> StdString {
88        String::from(self.into_boxed_str())
89    }
90
91    /// Convert to a mutable string type, cloning the data if necessary.
92    #[inline]
93    pub fn into_boxed_str(self) -> BoxedStr {
94        self.inner.into_boxed_str()
95    }
96
97    /// Convert to a Cow str
98    #[inline]
99    pub fn into_cow_str(self) -> Cow<'static, str> {
100        self.inner.into_cow_str()
101    }
102}
103
104impl KStringInner {
105    #[inline]
106    fn as_ref(&self) -> KStringRef<'_> {
107        match self {
108            Self::Singleton(s) => KStringRef::from_static(s),
109            Self::Inline(s) => KStringRef::from_ref(s.as_str()),
110            Self::Owned(s) => KStringRef::from_ref(s),
111        }
112    }
113
114    #[inline]
115    fn as_str(&self) -> &str {
116        match self {
117            Self::Singleton(s) => s,
118            Self::Inline(s) => s.as_str(),
119            Self::Owned(s) => &s,
120        }
121    }
122
123    #[inline]
124    fn into_boxed_str(self) -> BoxedStr {
125        match self {
126            Self::Singleton(s) => BoxedStr::from(s),
127            Self::Inline(s) => s.to_boxed_str(),
128            Self::Owned(s) => BoxedStr::from(s.as_ref()),
129        }
130    }
131
132    /// Convert to a Cow str
133    #[inline]
134    fn into_cow_str(self) -> Cow<'static, str> {
135        match self {
136            Self::Singleton(s) => Cow::Borrowed(s),
137            Self::Inline(s) => Cow::Owned(s.to_boxed_str().into()),
138            Self::Owned(s) => Cow::Owned(s.as_ref().into()),
139        }
140    }
141}
142
143// Explicit to avoid inlining which cuts clone times in half.
144//
145// An automatically derived `clone()` has 10ns overhead while the explicit `Deref`/`as_str` has
146// none of that.  Being explicit and removing the `#[inline]` attribute dropped the overhead to
147// 5ns.
148//
149// My only guess is that the `clone()` calls we delegate to are just that much bigger than
150// `as_str()` that, when combined with a jump table, is blowing the icache, slowing things down.
151impl Clone for KStringInner {
152    fn clone(&self) -> Self {
153        match self {
154            Self::Singleton(s) => Self::Singleton(s),
155            Self::Inline(s) => Self::Inline(*s),
156            Self::Owned(s) => Self::Owned(s.clone()),
157        }
158    }
159}
160
161impl std::ops::Deref for KString {
162    type Target = str;
163
164    #[inline]
165    fn deref(&self) -> &str {
166        self.as_str()
167    }
168}
169
170impl Eq for KString {}
171
172impl<'s> PartialEq<KString> for KString {
173    #[inline]
174    fn eq(&self, other: &KString) -> bool {
175        PartialEq::eq(self.as_str(), other.as_str())
176    }
177}
178
179impl<'s> PartialEq<str> for KString {
180    #[inline]
181    fn eq(&self, other: &str) -> bool {
182        PartialEq::eq(self.as_str(), other)
183    }
184}
185
186impl<'s> PartialEq<&'s str> for KString {
187    #[inline]
188    fn eq(&self, other: &&str) -> bool {
189        PartialEq::eq(self.as_str(), *other)
190    }
191}
192
193impl<'s> PartialEq<String> for KString {
194    #[inline]
195    fn eq(&self, other: &StdString) -> bool {
196        PartialEq::eq(self.as_str(), other.as_str())
197    }
198}
199
200impl Ord for KString {
201    #[inline]
202    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
203        self.as_str().cmp(other.as_str())
204    }
205}
206
207impl PartialOrd for KString {
208    #[inline]
209    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
210        self.as_str().partial_cmp(other.as_str())
211    }
212}
213
214impl std::hash::Hash for KString {
215    #[inline]
216    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
217        self.as_str().hash(state);
218    }
219}
220
221impl fmt::Debug for KString {
222    #[inline]
223    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
224        fmt::Debug::fmt(&self.inner, f)
225    }
226}
227
228impl fmt::Display for KString {
229    #[inline]
230    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
231        fmt::Display::fmt(self.as_str(), f)
232    }
233}
234
235impl AsRef<str> for KString {
236    #[inline]
237    fn as_ref(&self) -> &str {
238        self.as_str()
239    }
240}
241
242impl AsRef<[u8]> for KString {
243    #[inline]
244    fn as_ref(&self) -> &[u8] {
245        self.as_bytes()
246    }
247}
248
249impl AsRef<std::ffi::OsStr> for KString {
250    #[inline]
251    fn as_ref(&self) -> &std::ffi::OsStr {
252        (&**self).as_ref()
253    }
254}
255
256impl AsRef<std::path::Path> for KString {
257    #[inline]
258    fn as_ref(&self) -> &std::path::Path {
259        std::path::Path::new(self)
260    }
261}
262
263impl std::borrow::Borrow<str> for KString {
264    #[inline]
265    fn borrow(&self) -> &str {
266        self.as_str()
267    }
268}
269
270impl Default for KString {
271    #[inline]
272    fn default() -> Self {
273        Self::from_static("")
274    }
275}
276
277impl<'s> From<KStringRef<'s>> for KString {
278    #[inline]
279    fn from(other: KStringRef<'s>) -> Self {
280        other.to_owned()
281    }
282}
283
284impl<'s> From<&'s KStringRef<'s>> for KString {
285    #[inline]
286    fn from(other: &'s KStringRef<'s>) -> Self {
287        other.to_owned()
288    }
289}
290
291impl<'s> From<KStringCow<'s>> for KString {
292    #[inline]
293    fn from(other: KStringCow<'s>) -> Self {
294        other.into_owned()
295    }
296}
297
298impl<'s> From<&'s KStringCow<'s>> for KString {
299    #[inline]
300    fn from(other: &'s KStringCow<'s>) -> Self {
301        other.clone().into_owned()
302    }
303}
304
305impl From<StdString> for KString {
306    #[inline]
307    fn from(other: StdString) -> Self {
308        Self::from_string(other)
309    }
310}
311
312impl<'s> From<&'s StdString> for KString {
313    #[inline]
314    fn from(other: &'s StdString) -> Self {
315        Self::from_ref(other)
316    }
317}
318
319impl From<BoxedStr> for KString {
320    #[inline]
321    fn from(other: BoxedStr) -> Self {
322        Self::from_boxed(other)
323    }
324}
325
326impl<'s> From<&'s BoxedStr> for KString {
327    #[inline]
328    fn from(other: &'s BoxedStr) -> Self {
329        Self::from_ref(other)
330    }
331}
332
333impl From<&'static str> for KString {
334    #[inline]
335    fn from(other: &'static str) -> Self {
336        Self::from_static(other)
337    }
338}
339
340impl std::str::FromStr for KString {
341    type Err = std::convert::Infallible;
342    #[inline]
343    fn from_str(s: &str) -> Result<Self, Self::Err> {
344        Ok(KString::from_ref(s))
345    }
346}
347
348#[cfg(feature = "serde")]
349impl serde::Serialize for KString {
350    #[inline]
351    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
352    where
353        S: serde::Serializer,
354    {
355        serializer.serialize_str(self.as_str())
356    }
357}
358
359#[cfg(feature = "serde")]
360impl<'de> serde::Deserialize<'de> for KString {
361    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
362    where
363        D: serde::Deserializer<'de>,
364    {
365        deserializer.deserialize_string(StringVisitor)
366    }
367}
368
369#[cfg(feature = "serde")]
370struct StringVisitor;
371
372#[cfg(feature = "serde")]
373impl<'de> serde::de::Visitor<'de> for StringVisitor {
374    type Value = KString;
375
376    fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
377        formatter.write_str("a string")
378    }
379
380    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
381    where
382        E: serde::de::Error,
383    {
384        Ok(KString::from_ref(v))
385    }
386
387    fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
388    where
389        E: serde::de::Error,
390    {
391        Ok(KString::from_string(v))
392    }
393
394    fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
395    where
396        E: serde::de::Error,
397    {
398        match std::str::from_utf8(v) {
399            Ok(s) => Ok(KString::from_ref(s)),
400            Err(_) => Err(serde::de::Error::invalid_value(
401                serde::de::Unexpected::Bytes(v),
402                &self,
403            )),
404        }
405    }
406
407    fn visit_byte_buf<E>(self, v: Vec<u8>) -> Result<Self::Value, E>
408    where
409        E: serde::de::Error,
410    {
411        match String::from_utf8(v) {
412            Ok(s) => Ok(KString::from_string(s)),
413            Err(e) => Err(serde::de::Error::invalid_value(
414                serde::de::Unexpected::Bytes(&e.into_bytes()),
415                &self,
416            )),
417        }
418    }
419}
420
421#[cfg(test)]
422mod test {
423    use super::*;
424
425    #[test]
426    fn test_size() {
427        println!("KString: {}", std::mem::size_of::<KString>());
428    }
429}