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