Skip to main content

cheetah_string/
cheetah_str.rs

1use alloc::borrow::Cow;
2use alloc::string::{ParseError, String, ToString};
3use alloc::sync::Arc;
4use core::borrow::Borrow;
5use core::cmp::Ordering;
6use core::fmt;
7use core::hash::{Hash, Hasher};
8use core::ops::Deref;
9use core::str::{self, FromStr};
10
11use crate::CheetahString;
12
13const INLINE_CAPACITY: usize = 23;
14
15/// Immutable, clone-cheap string value for key/name/topic style workloads.
16///
17/// `CheetahStr` intentionally has no mutation API. Use [`CheetahBuilder`] or
18/// [`CheetahString`] when the value is still being constructed.
19///
20/// [`CheetahBuilder`]: crate::CheetahBuilder
21#[derive(Clone)]
22pub struct CheetahStr {
23    inner: Repr,
24}
25
26#[derive(Clone)]
27enum Repr {
28    Inline {
29        len: u8,
30        data: [u8; INLINE_CAPACITY],
31    },
32    Static(&'static str),
33    Shared(Arc<str>),
34}
35
36impl CheetahStr {
37    /// Creates an empty immutable string.
38    #[inline]
39    pub const fn empty() -> Self {
40        Self {
41            inner: Repr::Inline {
42                len: 0,
43                data: [0; INLINE_CAPACITY],
44            },
45        }
46    }
47
48    /// Creates an empty immutable string.
49    #[inline]
50    pub fn new() -> Self {
51        Self::empty()
52    }
53
54    /// Creates a zero-copy immutable string from a static string slice.
55    #[inline]
56    pub const fn from_static_str(s: &'static str) -> Self {
57        Self {
58            inner: Repr::Static(s),
59        }
60    }
61
62    /// Creates an immutable string from a borrowed string slice.
63    #[inline]
64    pub fn from_slice(s: &str) -> Self {
65        if s.len() <= INLINE_CAPACITY {
66            let mut data = [0u8; INLINE_CAPACITY];
67            data[..s.len()].copy_from_slice(s.as_bytes());
68            Self {
69                inner: Repr::Inline {
70                    len: s.len() as u8,
71                    data,
72                },
73            }
74        } else {
75            Self {
76                inner: Repr::Shared(Arc::from(s)),
77            }
78        }
79    }
80
81    /// Creates an immutable string from owned storage.
82    #[inline]
83    pub fn from_string(s: String) -> Self {
84        if s.len() <= INLINE_CAPACITY {
85            Self::from_slice(&s)
86        } else {
87            Self {
88                inner: Repr::Shared(s.into_boxed_str().into()),
89            }
90        }
91    }
92
93    /// Returns the string slice.
94    #[inline]
95    pub fn as_str(&self) -> &str {
96        match &self.inner {
97            Repr::Inline { len, data } => {
98                // SAFETY: Inline data is copied only from valid UTF-8 strings.
99                unsafe { str::from_utf8_unchecked(&data[..*len as usize]) }
100            }
101            Repr::Static(s) => s,
102            Repr::Shared(s) => s.as_ref(),
103        }
104    }
105
106    /// Returns the UTF-8 bytes.
107    #[inline]
108    pub fn as_bytes(&self) -> &[u8] {
109        self.as_str().as_bytes()
110    }
111
112    /// Returns the byte length.
113    #[inline]
114    pub fn len(&self) -> usize {
115        self.as_str().len()
116    }
117
118    /// Returns whether this string is empty.
119    #[inline]
120    pub fn is_empty(&self) -> bool {
121        self.as_str().is_empty()
122    }
123}
124
125impl Default for CheetahStr {
126    #[inline]
127    fn default() -> Self {
128        Self::empty()
129    }
130}
131
132impl From<&str> for CheetahStr {
133    #[inline]
134    fn from(value: &str) -> Self {
135        Self::from_slice(value)
136    }
137}
138
139impl From<String> for CheetahStr {
140    #[inline]
141    fn from(value: String) -> Self {
142        Self::from_string(value)
143    }
144}
145
146impl From<Cow<'static, str>> for CheetahStr {
147    #[inline]
148    fn from(value: Cow<'static, str>) -> Self {
149        match value {
150            Cow::Borrowed(s) => Self::from_static_str(s),
151            Cow::Owned(s) => Self::from_string(s),
152        }
153    }
154}
155
156impl From<&CheetahString> for CheetahStr {
157    #[inline]
158    fn from(value: &CheetahString) -> Self {
159        Self::from_slice(value.as_str())
160    }
161}
162
163impl From<CheetahString> for CheetahStr {
164    #[inline]
165    fn from(value: CheetahString) -> Self {
166        Self::from_string(String::from(value))
167    }
168}
169
170impl From<CheetahStr> for String {
171    #[inline]
172    fn from(value: CheetahStr) -> Self {
173        value.as_str().to_string()
174    }
175}
176
177impl FromStr for CheetahStr {
178    type Err = ParseError;
179
180    #[inline]
181    fn from_str(s: &str) -> Result<Self, Self::Err> {
182        Ok(Self::from_slice(s))
183    }
184}
185
186impl Deref for CheetahStr {
187    type Target = str;
188
189    #[inline]
190    fn deref(&self) -> &Self::Target {
191        self.as_str()
192    }
193}
194
195impl AsRef<str> for CheetahStr {
196    #[inline]
197    fn as_ref(&self) -> &str {
198        self.as_str()
199    }
200}
201
202impl AsRef<[u8]> for CheetahStr {
203    #[inline]
204    fn as_ref(&self) -> &[u8] {
205        self.as_bytes()
206    }
207}
208
209impl Borrow<str> for CheetahStr {
210    #[inline]
211    fn borrow(&self) -> &str {
212        self.as_str()
213    }
214}
215
216impl fmt::Display for CheetahStr {
217    #[inline]
218    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
219        self.as_str().fmt(f)
220    }
221}
222
223impl fmt::Debug for CheetahStr {
224    #[inline]
225    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
226        fmt::Debug::fmt(self.as_str(), f)
227    }
228}
229
230impl Hash for CheetahStr {
231    #[inline]
232    fn hash<H: Hasher>(&self, state: &mut H) {
233        self.as_str().hash(state);
234    }
235}
236
237impl PartialEq for CheetahStr {
238    #[inline]
239    fn eq(&self, other: &Self) -> bool {
240        self.as_str() == other.as_str()
241    }
242}
243
244impl PartialEq<str> for CheetahStr {
245    #[inline]
246    fn eq(&self, other: &str) -> bool {
247        self.as_str() == other
248    }
249}
250
251impl PartialEq<&str> for CheetahStr {
252    #[inline]
253    fn eq(&self, other: &&str) -> bool {
254        self.as_str() == *other
255    }
256}
257
258impl PartialEq<CheetahStr> for str {
259    #[inline]
260    fn eq(&self, other: &CheetahStr) -> bool {
261        self == other.as_str()
262    }
263}
264
265impl PartialEq<CheetahStr> for &str {
266    #[inline]
267    fn eq(&self, other: &CheetahStr) -> bool {
268        *self == other.as_str()
269    }
270}
271
272impl Eq for CheetahStr {}
273
274impl PartialOrd for CheetahStr {
275    #[inline]
276    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
277        Some(self.cmp(other))
278    }
279}
280
281impl Ord for CheetahStr {
282    #[inline]
283    fn cmp(&self, other: &Self) -> Ordering {
284        self.as_str().cmp(other.as_str())
285    }
286}