1 use std::borrow::{Borrow, ToOwned};
2 use std::convert::{AsRef, TryFrom};
3 use std::fmt;
4 use std::hash::{Hash, Hasher};
5 use std::ops::Deref;
6 use std::str::from_utf8;
7 
8 const MAX_INLINE_STR_LEN: usize = 3 * std::mem::size_of::<isize>() - 1;
9 
10 /// Returned when trying to convert a `&str` into a `InlineStr`
11 /// but it fails because it doesn't fit.
12 #[derive(Debug)]
13 pub struct StringTooLongError;
14 
15 /// An inline string that can contain almost three words
16 /// of utf-8 text.
17 #[derive(Debug, Clone, Copy, Eq)]
18 pub struct InlineStr {
19     inner: [u8; MAX_INLINE_STR_LEN],
20 }
21 
22 impl<'a> AsRef<str> for InlineStr {
as_ref(&self) -> &str23     fn as_ref(&self) -> &str {
24         self.deref()
25     }
26 }
27 
28 impl Hash for InlineStr {
hash<H: Hasher>(&self, state: &mut H)29     fn hash<H: Hasher>(&self, state: &mut H) {
30         self.deref().hash(state);
31     }
32 }
33 
34 impl From<char> for InlineStr {
from(c: char) -> Self35     fn from(c: char) -> Self {
36         let mut inner = [0u8; MAX_INLINE_STR_LEN];
37         c.encode_utf8(&mut inner);
38         inner[MAX_INLINE_STR_LEN - 1] = c.len_utf8() as u8;
39         Self { inner }
40     }
41 }
42 
43 impl<'a> std::cmp::PartialEq<InlineStr> for InlineStr {
eq(&self, other: &InlineStr) -> bool44     fn eq(&self, other: &InlineStr) -> bool {
45         self.deref() == other.deref()
46     }
47 }
48 
49 impl TryFrom<&str> for InlineStr {
50     type Error = StringTooLongError;
51 
try_from(s: &str) -> Result<InlineStr, StringTooLongError>52     fn try_from(s: &str) -> Result<InlineStr, StringTooLongError> {
53         let len = s.len();
54         if len < MAX_INLINE_STR_LEN {
55             let mut inner = [0u8; MAX_INLINE_STR_LEN];
56             inner[..len].copy_from_slice(s.as_bytes());
57             inner[MAX_INLINE_STR_LEN - 1] = len as u8;
58             Ok(Self { inner })
59         } else {
60             Err(StringTooLongError)
61         }
62     }
63 }
64 
65 impl Deref for InlineStr {
66     type Target = str;
67 
deref(&self) -> &str68     fn deref(&self) -> &str {
69         let len = self.inner[MAX_INLINE_STR_LEN - 1] as usize;
70         from_utf8(&self.inner[..len]).unwrap()
71     }
72 }
73 
74 impl fmt::Display for InlineStr {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result75     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)]
85 pub 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 impl<'a> AsRef<str> for CowStr<'a> {
as_ref(&self) -> &str95     fn as_ref(&self) -> &str {
96         self.deref()
97     }
98 }
99 
100 impl<'a> Hash for CowStr<'a> {
hash<H: Hasher>(&self, state: &mut H)101     fn hash<H: Hasher>(&self, state: &mut H) {
102         self.deref().hash(state);
103     }
104 }
105 
106 impl<'a> std::clone::Clone for CowStr<'a> {
clone(&self) -> Self107     fn clone(&self) -> Self {
108         match self {
109             CowStr::Boxed(s) => match InlineStr::try_from(&**s) {
110                 Ok(inline) => CowStr::Inlined(inline),
111                 Err(..) => CowStr::Boxed(s.clone()),
112             },
113             CowStr::Borrowed(s) => CowStr::Borrowed(s),
114             CowStr::Inlined(s) => CowStr::Inlined(*s),
115         }
116     }
117 }
118 
119 impl<'a> std::cmp::PartialEq<CowStr<'a>> for CowStr<'a> {
eq(&self, other: &CowStr) -> bool120     fn eq(&self, other: &CowStr) -> bool {
121         self.deref() == other.deref()
122     }
123 }
124 
125 impl<'a> From<&'a str> for CowStr<'a> {
from(s: &'a str) -> Self126     fn from(s: &'a str) -> Self {
127         CowStr::Borrowed(s)
128     }
129 }
130 
131 impl<'a> From<String> for CowStr<'a> {
from(s: String) -> Self132     fn from(s: String) -> Self {
133         CowStr::Boxed(s.into_boxed_str())
134     }
135 }
136 
137 impl<'a> From<char> for CowStr<'a> {
from(c: char) -> Self138     fn from(c: char) -> Self {
139         CowStr::Inlined(c.into())
140     }
141 }
142 
143 impl<'a> Deref for CowStr<'a> {
144     type Target = str;
145 
deref(&self) -> &str146     fn deref(&self) -> &str {
147         match self {
148             CowStr::Boxed(ref b) => &*b,
149             CowStr::Borrowed(b) => b,
150             CowStr::Inlined(ref s) => s.deref(),
151         }
152     }
153 }
154 
155 impl<'a> Borrow<str> for CowStr<'a> {
borrow(&self) -> &str156     fn borrow(&self) -> &str {
157         self.deref()
158     }
159 }
160 
161 impl<'a> CowStr<'a> {
into_string(self) -> String162     pub fn into_string(self) -> String {
163         match self {
164             CowStr::Boxed(b) => b.into(),
165             CowStr::Borrowed(b) => b.to_owned(),
166             CowStr::Inlined(s) => s.deref().to_owned(),
167         }
168     }
169 }
170 
171 impl<'a> fmt::Display for CowStr<'a> {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result172     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
173         write!(f, "{}", self.as_ref())
174     }
175 }
176 
177 #[cfg(test)]
178 mod test_special_string {
179     use super::*;
180 
181     #[test]
inlinestr_ascii()182     fn inlinestr_ascii() {
183         let s: InlineStr = 'a'.into();
184         assert_eq!("a", s.deref());
185     }
186 
187     #[test]
inlinestr_unicode()188     fn inlinestr_unicode() {
189         let s: InlineStr = '��'.into();
190         assert_eq!("��", s.deref());
191     }
192 
193     #[test]
cowstr_size()194     fn cowstr_size() {
195         let size = std::mem::size_of::<CowStr>();
196         let word_size = std::mem::size_of::<isize>();
197         assert_eq!(3 * word_size, size);
198     }
199 
200     #[test]
cowstr_char_to_string()201     fn cowstr_char_to_string() {
202         let c = '藏';
203         let smort: CowStr = c.into();
204         let owned: String = smort.to_string();
205         let expected = "藏".to_owned();
206         assert_eq!(expected, owned);
207     }
208 
209     #[test]
max_inline_str_len_atleast_five()210     fn max_inline_str_len_atleast_five() {
211         // we need 4 bytes to store a char and then one more to store
212         // its length
213         assert!(MAX_INLINE_STR_LEN >= 5);
214     }
215 
216     #[test]
217     #[cfg(target_pointer_width = "64")]
inlinestr_fits_twentytwo()218     fn inlinestr_fits_twentytwo() {
219         let s = "0123456789abcdefghijkl";
220         let stack_str = InlineStr::try_from(s).unwrap();
221         assert_eq!(stack_str.deref(), s);
222     }
223 
224     #[test]
225     #[cfg(target_pointer_width = "64")]
inlinestr_not_fits_twentythree()226     fn inlinestr_not_fits_twentythree() {
227         let s = "0123456789abcdefghijklm";
228         let _stack_str = InlineStr::try_from(s).unwrap_err();
229     }
230 
231     #[test]
232     #[cfg(target_pointer_width = "64")]
small_boxed_str_clones_to_stack()233     fn small_boxed_str_clones_to_stack() {
234         let s = "0123456789abcde".to_owned();
235         let smort: CowStr = s.into();
236         let smort_clone = smort.clone();
237 
238         if let CowStr::Inlined(..) = smort_clone {
239         } else {
240             panic!("Expected a Inlined variant!");
241         }
242     }
243 }
244