pulldown_cmark/
strings.rs

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 = [0u8; MAX_INLINE_STR_LEN];
37        c.encode_utf8(&mut inner);
38        let len = 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 = 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            let len = 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 = 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) => match InlineStr::try_from(&**s) {
169                Ok(inline) => CowStr::Inlined(inline),
170                Err(..) => CowStr::Boxed(s.clone()),
171            },
172            CowStr::Borrowed(s) => CowStr::Borrowed(s),
173            CowStr::Inlined(s) => 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) => CowStr::Borrowed(s),
206            Cow::Owned(s) => 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) => Cow::Owned(s.to_string()),
215            CowStr::Inlined(s) => Cow::Owned(s.to_string()),
216            CowStr::Borrowed(s) => 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> From<CowStr<'a>> for String {
228    fn from(s: CowStr<'a>) -> Self {
229        match s {
230            CowStr::Boxed(s) => s.into(),
231            CowStr::Inlined(s) => s.as_ref().into(),
232            CowStr::Borrowed(s) => s.into(),
233        }
234    }
235}
236
237impl<'a> Deref for CowStr<'a> {
238    type Target = str;
239
240    fn deref(&self) -> &str {
241        match self {
242            CowStr::Boxed(ref b) => b,
243            CowStr::Borrowed(b) => b,
244            CowStr::Inlined(ref s) => s.deref(),
245        }
246    }
247}
248
249impl<'a> Borrow<str> for CowStr<'a> {
250    fn borrow(&self) -> &str {
251        self.deref()
252    }
253}
254
255impl<'a> CowStr<'a> {
256    pub fn into_string(self) -> String {
257        match self {
258            CowStr::Boxed(b) => b.into(),
259            CowStr::Borrowed(b) => b.to_owned(),
260            CowStr::Inlined(s) => s.deref().to_owned(),
261        }
262    }
263
264    pub fn into_static(self) -> CowStr<'static> {
265        match self {
266            CowStr::Boxed(b) => CowStr::Boxed(b),
267            CowStr::Borrowed(b) => match InlineStr::try_from(b) {
268                Ok(inline) => CowStr::Inlined(inline),
269                Err(_) => CowStr::Boxed(b.into()),
270            },
271            CowStr::Inlined(s) => CowStr::Inlined(s),
272        }
273    }
274}
275
276impl<'a> fmt::Display for CowStr<'a> {
277    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
278        write!(f, "{}", self.as_ref())
279    }
280}
281
282#[cfg(test)]
283mod test_special_string {
284    use super::*;
285
286    #[test]
287    fn inlinestr_ascii() {
288        let s: InlineStr = 'a'.into();
289        assert_eq!("a", s.deref());
290    }
291
292    #[test]
293    fn inlinestr_unicode() {
294        let s: InlineStr = '🍔'.into();
295        assert_eq!("🍔", s.deref());
296    }
297
298    #[test]
299    fn cowstr_size() {
300        let size = std::mem::size_of::<CowStr>();
301        let word_size = std::mem::size_of::<isize>();
302        assert_eq!(3 * word_size, size);
303    }
304
305    #[test]
306    fn cowstr_char_to_string() {
307        let c = '藏';
308        let smort: CowStr = c.into();
309        let owned: String = smort.to_string();
310        let expected = "藏".to_owned();
311        assert_eq!(expected, owned);
312    }
313
314    #[test]
315    fn max_inline_str_len_atleast_four() {
316        // we need 4 bytes to store a char
317        assert!(MAX_INLINE_STR_LEN >= 4);
318    }
319
320    #[test]
321    #[cfg(target_pointer_width = "64")]
322    fn inlinestr_fits_twentytwo() {
323        let s = "0123456789abcdefghijkl";
324        let stack_str = InlineStr::try_from(s).unwrap();
325        assert_eq!(stack_str.deref(), s);
326    }
327
328    #[test]
329    #[cfg(target_pointer_width = "64")]
330    fn inlinestr_not_fits_twentythree() {
331        let s = "0123456789abcdefghijklm";
332        let _stack_str = InlineStr::try_from(s).unwrap_err();
333    }
334
335    #[test]
336    #[cfg(target_pointer_width = "64")]
337    fn small_boxed_str_clones_to_stack() {
338        let s = "0123456789abcde".to_owned();
339        let smort: CowStr = s.into();
340        let smort_clone = smort.clone();
341
342        if let CowStr::Inlined(..) = smort_clone {
343        } else {
344            panic!("Expected a Inlined variant!");
345        }
346    }
347
348    #[test]
349    fn cow_to_cow_str() {
350        let s = "some text";
351        let cow = Cow::Borrowed(s);
352        let actual = CowStr::from(cow);
353        let expected = CowStr::Borrowed(s);
354        assert_eq!(actual, expected);
355        assert!(variant_eq(&actual, &expected));
356
357        let s = "some text".to_string();
358        let cow: Cow<str> = Cow::Owned(s.clone());
359        let actual = CowStr::from(cow);
360        let expected = CowStr::Boxed(s.into_boxed_str());
361        assert_eq!(actual, expected);
362        assert!(variant_eq(&actual, &expected));
363    }
364
365    #[test]
366    fn cow_str_to_cow() {
367        let s = "some text";
368        let cow_str = CowStr::Borrowed(s);
369        let actual = Cow::from(cow_str);
370        let expected = Cow::Borrowed(s);
371        assert_eq!(actual, expected);
372        assert!(variant_eq(&actual, &expected));
373
374        let s = "s";
375        let inline_str: InlineStr = InlineStr::try_from(s).unwrap();
376        let cow_str = CowStr::Inlined(inline_str);
377        let actual = Cow::from(cow_str);
378        let expected: Cow<str> = Cow::Owned(s.to_string());
379        assert_eq!(actual, expected);
380        assert!(variant_eq(&actual, &expected));
381
382        let s = "s";
383        let cow_str = CowStr::Boxed(s.to_string().into_boxed_str());
384        let actual = Cow::from(cow_str);
385        let expected: Cow<str> = Cow::Owned(s.to_string());
386        assert_eq!(actual, expected);
387        assert!(variant_eq(&actual, &expected));
388    }
389
390    #[test]
391    fn cow_str_to_string() {
392        let s = "some text";
393        let cow_str = CowStr::Borrowed(s);
394        let actual = String::from(cow_str);
395        let expected = String::from("some text");
396        assert_eq!(actual, expected);
397
398        let s = "s";
399        let inline_str: InlineStr = InlineStr::try_from(s).unwrap();
400        let cow_str = CowStr::Inlined(inline_str);
401        let actual = String::from(cow_str);
402        let expected = String::from("s");
403        assert_eq!(actual, expected);
404
405        let s = "s";
406        let cow_str = CowStr::Boxed(s.to_string().into_boxed_str());
407        let actual = String::from(cow_str);
408        let expected = String::from("s");
409        assert_eq!(actual, expected);
410    }
411
412    #[test]
413    fn cow_char_to_cow_str() {
414        let c = 'c';
415        let cow: Cow<char> = Cow::Owned(c);
416        let actual = CowStr::from(cow);
417        let expected = CowStr::Inlined(InlineStr::from(c));
418        assert_eq!(actual, expected);
419        assert!(variant_eq(&actual, &expected));
420
421        let c = 'c';
422        let cow: Cow<char> = Cow::Borrowed(&c);
423        let actual = CowStr::from(cow);
424        let expected = CowStr::Inlined(InlineStr::from(c));
425        assert_eq!(actual, expected);
426        assert!(variant_eq(&actual, &expected));
427    }
428
429    fn variant_eq<T>(a: &T, b: &T) -> bool {
430        std::mem::discriminant(a) == std::mem::discriminant(b)
431    }
432}