1use std::borrow::{Borrow, Cow};
2use std::fmt;
3use std::hash::{Hash, Hasher};
4use std::ops::Deref;
5use std::str::from_utf8;
6
7const MAX_INLINE_STR_LEN: usize = 3 * std::mem::size_of::<isize>() - 2;
8
9/// Returned when trying to convert a `&str` into a `InlineStr`
10/// but it fails because it doesn't fit.
11#[derive(Debug)]
12pub struct StringTooLongError;
13
14/// An inline string that can contain almost three words
15/// of utf-8 text.
16#[derive(Debug, Clone, Copy, Eq)]
17pub struct InlineStr {
18 inner: [u8; MAX_INLINE_STR_LEN],
19 len: u8,
20}
21
22impl AsRef<str> for InlineStr {
23 fn as_ref(&self) -> &str {
24 self.deref()
25 }
26}
27
28impl Hash for InlineStr {
29 fn hash<H: Hasher>(&self, state: &mut H) {
30 self.deref().hash(state);
31 }
32}
33
34impl From<char> for InlineStr {
35 fn from(c: char) -> Self {
36 let mut inner: [u8; 22] = [0u8; MAX_INLINE_STR_LEN];
37 c.encode_utf8(&mut inner);
38 let len: u8 = c.len_utf8() as u8;
39 Self { inner, len }
40 }
41}
42
43impl std::cmp::PartialEq<InlineStr> for InlineStr {
44 fn eq(&self, other: &InlineStr) -> bool {
45 self.deref() == other.deref()
46 }
47}
48
49impl TryFrom<&str> for InlineStr {
50 type Error = StringTooLongError;
51
52 fn try_from(s: &str) -> Result<InlineStr, StringTooLongError> {
53 let len: usize = s.len();
54 if len <= MAX_INLINE_STR_LEN {
55 let mut inner: [u8; 22] = [0u8; MAX_INLINE_STR_LEN];
56 inner[..len].copy_from_slice(src:s.as_bytes());
57 let len: u8 = len as u8;
58 Ok(Self { inner, len })
59 } else {
60 Err(StringTooLongError)
61 }
62 }
63}
64
65impl Deref for InlineStr {
66 type Target = str;
67
68 fn deref(&self) -> &str {
69 let len: usize = self.len as usize;
70 from_utf8(&self.inner[..len]).unwrap()
71 }
72}
73
74impl fmt::Display for InlineStr {
75 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76 write!(f, "{}", self.as_ref())
77 }
78}
79
80/// A copy-on-write string that can be owned, borrowed
81/// or inlined.
82///
83/// It is three words long.
84#[derive(Debug, Eq)]
85pub enum CowStr<'a> {
86 /// An owned, immutable string.
87 Boxed(Box<str>),
88 /// A borrowed string.
89 Borrowed(&'a str),
90 /// A short inline string.
91 Inlined(InlineStr),
92}
93
94#[cfg(feature = "serde")]
95mod serde_impl {
96 use super::CowStr;
97 use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
98 use std::fmt;
99
100 impl<'a> Serialize for CowStr<'a> {
101 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
102 where
103 S: Serializer,
104 {
105 serializer.serialize_str(self.as_ref())
106 }
107 }
108
109 struct CowStrVisitor;
110
111 impl<'de> de::Visitor<'de> for CowStrVisitor {
112 type Value = CowStr<'de>;
113
114 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
115 formatter.write_str("a string")
116 }
117
118 fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E>
119 where
120 E: de::Error,
121 {
122 Ok(CowStr::Borrowed(v))
123 }
124
125 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
126 where
127 E: de::Error,
128 {
129 match v.try_into() {
130 Ok(it) => Ok(CowStr::Inlined(it)),
131 Err(_) => Ok(CowStr::Boxed(String::from(v).into_boxed_str())),
132 }
133 }
134
135 fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
136 where
137 E: de::Error,
138 {
139 Ok(CowStr::Boxed(v.into_boxed_str()))
140 }
141 }
142
143 impl<'a, 'de: 'a> Deserialize<'de> for CowStr<'a> {
144 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
145 where
146 D: Deserializer<'de>,
147 {
148 deserializer.deserialize_str(CowStrVisitor)
149 }
150 }
151}
152
153impl<'a> AsRef<str> for CowStr<'a> {
154 fn as_ref(&self) -> &str {
155 self.deref()
156 }
157}
158
159impl<'a> Hash for CowStr<'a> {
160 fn hash<H: Hasher>(&self, state: &mut H) {
161 self.deref().hash(state);
162 }
163}
164
165impl<'a> std::clone::Clone for CowStr<'a> {
166 fn clone(&self) -> Self {
167 match self {
168 CowStr::Boxed(s: &Box) => match InlineStr::try_from(&**s) {
169 Ok(inline: InlineStr) => CowStr::Inlined(inline),
170 Err(..) => CowStr::Boxed(s.clone()),
171 },
172 CowStr::Borrowed(s: &&str) => CowStr::Borrowed(s),
173 CowStr::Inlined(s: &InlineStr) => CowStr::Inlined(*s),
174 }
175 }
176}
177
178impl<'a> std::cmp::PartialEq<CowStr<'a>> for CowStr<'a> {
179 fn eq(&self, other: &CowStr<'_>) -> bool {
180 self.deref() == other.deref()
181 }
182}
183
184impl<'a> From<&'a str> for CowStr<'a> {
185 fn from(s: &'a str) -> Self {
186 CowStr::Borrowed(s)
187 }
188}
189
190impl<'a> From<String> for CowStr<'a> {
191 fn from(s: String) -> Self {
192 CowStr::Boxed(s.into_boxed_str())
193 }
194}
195
196impl<'a> From<char> for CowStr<'a> {
197 fn from(c: char) -> Self {
198 CowStr::Inlined(c.into())
199 }
200}
201
202impl<'a> From<Cow<'a, str>> for CowStr<'a> {
203 fn from(s: Cow<'a, str>) -> Self {
204 match s {
205 Cow::Borrowed(s: &str) => CowStr::Borrowed(s),
206 Cow::Owned(s: String) => CowStr::Boxed(s.into_boxed_str()),
207 }
208 }
209}
210
211impl<'a> From<CowStr<'a>> for Cow<'a, str> {
212 fn from(s: CowStr<'a>) -> Self {
213 match s {
214 CowStr::Boxed(s: Box) => Cow::Owned(s.to_string()),
215 CowStr::Inlined(s: InlineStr) => Cow::Owned(s.to_string()),
216 CowStr::Borrowed(s: &str) => Cow::Borrowed(s),
217 }
218 }
219}
220
221impl<'a> From<Cow<'a, char>> for CowStr<'a> {
222 fn from(s: Cow<'a, char>) -> Self {
223 CowStr::Inlined(InlineStr::from(*s))
224 }
225}
226
227impl<'a> Deref for CowStr<'a> {
228 type Target = str;
229
230 fn deref(&self) -> &str {
231 match self {
232 CowStr::Boxed(ref b: &Box) => b,
233 CowStr::Borrowed(b: &&str) => b,
234 CowStr::Inlined(ref s: &InlineStr) => s.deref(),
235 }
236 }
237}
238
239impl<'a> Borrow<str> for CowStr<'a> {
240 fn borrow(&self) -> &str {
241 self.deref()
242 }
243}
244
245impl<'a> CowStr<'a> {
246 pub fn into_string(self) -> String {
247 match self {
248 CowStr::Boxed(b: Box) => b.into(),
249 CowStr::Borrowed(b: &str) => b.to_owned(),
250 CowStr::Inlined(s: InlineStr) => s.deref().to_owned(),
251 }
252 }
253}
254
255impl<'a> fmt::Display for CowStr<'a> {
256 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
257 write!(f, "{}", self.as_ref())
258 }
259}
260
261#[cfg(test)]
262mod test_special_string {
263 use super::*;
264
265 #[test]
266 fn inlinestr_ascii() {
267 let s: InlineStr = 'a'.into();
268 assert_eq!("a", s.deref());
269 }
270
271 #[test]
272 fn inlinestr_unicode() {
273 let s: InlineStr = '🍔'.into();
274 assert_eq!("🍔", s.deref());
275 }
276
277 #[test]
278 fn cowstr_size() {
279 let size = std::mem::size_of::<CowStr>();
280 let word_size = std::mem::size_of::<isize>();
281 assert_eq!(3 * word_size, size);
282 }
283
284 #[test]
285 fn cowstr_char_to_string() {
286 let c = '藏';
287 let smort: CowStr = c.into();
288 let owned: String = smort.to_string();
289 let expected = "藏".to_owned();
290 assert_eq!(expected, owned);
291 }
292
293 #[test]
294 fn max_inline_str_len_atleast_four() {
295 // we need 4 bytes to store a char
296 assert!(MAX_INLINE_STR_LEN >= 4);
297 }
298
299 #[test]
300 #[cfg(target_pointer_width = "64")]
301 fn inlinestr_fits_twentytwo() {
302 let s = "0123456789abcdefghijkl";
303 let stack_str = InlineStr::try_from(s).unwrap();
304 assert_eq!(stack_str.deref(), s);
305 }
306
307 #[test]
308 #[cfg(target_pointer_width = "64")]
309 fn inlinestr_not_fits_twentythree() {
310 let s = "0123456789abcdefghijklm";
311 let _stack_str = InlineStr::try_from(s).unwrap_err();
312 }
313
314 #[test]
315 #[cfg(target_pointer_width = "64")]
316 fn small_boxed_str_clones_to_stack() {
317 let s = "0123456789abcde".to_owned();
318 let smort: CowStr = s.into();
319 let smort_clone = smort.clone();
320
321 if let CowStr::Inlined(..) = smort_clone {
322 } else {
323 panic!("Expected a Inlined variant!");
324 }
325 }
326
327 #[test]
328 fn cow_to_cow_str() {
329 let s = "some text";
330 let cow = Cow::Borrowed(s);
331 let actual = CowStr::from(cow);
332 let expected = CowStr::Borrowed(s);
333 assert_eq!(actual, expected);
334 assert!(variant_eq(&actual, &expected));
335
336 let s = "some text".to_string();
337 let cow: Cow<str> = Cow::Owned(s.clone());
338 let actual = CowStr::from(cow);
339 let expected = CowStr::Boxed(s.into_boxed_str());
340 assert_eq!(actual, expected);
341 assert!(variant_eq(&actual, &expected));
342 }
343
344 #[test]
345 fn cow_str_to_cow() {
346 let s = "some text";
347 let cow_str = CowStr::Borrowed(s);
348 let actual = Cow::from(cow_str);
349 let expected = Cow::Borrowed(s);
350 assert_eq!(actual, expected);
351 assert!(variant_eq(&actual, &expected));
352
353 let s = "s";
354 let inline_str: InlineStr = InlineStr::try_from(s).unwrap();
355 let cow_str = CowStr::Inlined(inline_str);
356 let actual = Cow::from(cow_str);
357 let expected: Cow<str> = Cow::Owned(s.to_string());
358 assert_eq!(actual, expected);
359 assert!(variant_eq(&actual, &expected));
360
361 let s = "s";
362 let cow_str = CowStr::Boxed(s.to_string().into_boxed_str());
363 let actual = Cow::from(cow_str);
364 let expected: Cow<str> = Cow::Owned(s.to_string());
365 assert_eq!(actual, expected);
366 assert!(variant_eq(&actual, &expected));
367 }
368
369 #[test]
370 fn cow_char_to_cow_str() {
371 let c = 'c';
372 let cow: Cow<char> = Cow::Owned(c);
373 let actual = CowStr::from(cow);
374 let expected = CowStr::Inlined(InlineStr::from(c));
375 assert_eq!(actual, expected);
376 assert!(variant_eq(&actual, &expected));
377
378 let c = 'c';
379 let cow: Cow<char> = Cow::Borrowed(&c);
380 let actual = CowStr::from(cow);
381 let expected = CowStr::Inlined(InlineStr::from(c));
382 assert_eq!(actual, expected);
383 assert!(variant_eq(&actual, &expected));
384 }
385
386 fn variant_eq<T>(a: &T, b: &T) -> bool {
387 std::mem::discriminant(a) == std::mem::discriminant(b)
388 }
389}
390