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