smol_str/
lib.rs

1#![no_std]
2extern crate alloc;
3
4use alloc::{borrow::Cow, boxed::Box, string::String, sync::Arc};
5use core::{
6    borrow::Borrow,
7    cmp::{self, Ordering},
8    convert::Infallible,
9    fmt, hash, iter,
10    mem::transmute,
11    ops::Deref,
12    str::FromStr,
13};
14
15/// A `SmolStr` is a string type that has the following properties:
16///
17/// * `size_of::<SmolStr>() == 24` (therefor `== size_of::<String>()` on 64 bit platforms)
18/// * `Clone` is `O(1)`
19/// * Strings are stack-allocated if they are:
20///     * Up to 23 bytes long
21///     * Longer than 23 bytes, but substrings of `WS` (see below). Such strings consist
22///     solely of consecutive newlines, followed by consecutive spaces
23/// * If a string does not satisfy the aforementioned conditions, it is heap-allocated
24/// * Additionally, a `SmolStr` can be explicitely created from a `&'static str` without allocation
25///
26/// Unlike `String`, however, `SmolStr` is immutable. The primary use case for
27/// `SmolStr` is a good enough default storage for tokens of typical programming
28/// languages. Strings consisting of a series of newlines, followed by a series of
29/// whitespace are a typical pattern in computer programs because of indentation.
30/// Note that a specialized interner might be a better solution for some use cases.
31///
32/// `WS`: A string of 32 newlines followed by 128 spaces.
33pub struct SmolStr(Repr);
34
35impl Clone for SmolStr {
36    #[inline]
37    fn clone(&self) -> Self {
38        if !self.is_heap_allocated() {
39            return unsafe { core::ptr::read(self as *const SmolStr) };
40        }
41        Self(self.0.clone())
42    }
43}
44
45impl SmolStr {
46    #[deprecated = "Use `new_inline` instead"]
47    pub const fn new_inline_from_ascii(len: usize, bytes: &[u8]) -> SmolStr {
48        assert!(len <= INLINE_CAP);
49
50        const ZEROS: &[u8] = &[0; INLINE_CAP];
51
52        let mut buf = [0; INLINE_CAP];
53        macro_rules! s {
54            ($($idx:literal),*) => ( $(s!(set $idx);)* );
55            (set $idx:literal) => ({
56                let src: &[u8] = [ZEROS, bytes][($idx < len) as usize];
57                let byte = src[$idx];
58                let _is_ascii = [(); 128][byte as usize];
59                buf[$idx] = byte
60            });
61        }
62        s!(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22);
63        SmolStr(Repr::Inline {
64            // SAFETY: We know that `len` is less than or equal to the maximum value of `InlineSize`
65            // as we asserted it.
66            len: unsafe { InlineSize::transmute_from_u8(len as u8) },
67            buf,
68        })
69    }
70
71    /// Constructs inline variant of `SmolStr`.
72    ///
73    /// Panics if `text.len() > 23`.
74    #[inline]
75    pub const fn new_inline(text: &str) -> SmolStr {
76        assert!(text.len() <= INLINE_CAP); // avoids checks in loop
77
78        let mut buf = [0; INLINE_CAP];
79        let mut i = 0;
80        while i < text.len() {
81            buf[i] = text.as_bytes()[i];
82            i += 1
83        }
84        SmolStr(Repr::Inline {
85            // SAFETY: We know that `len` is less than or equal to the maximum value of `InlineSize`
86            // as we asserted it.
87            len: unsafe { InlineSize::transmute_from_u8(text.len() as u8) },
88            buf,
89        })
90    }
91
92    /// Constructs a `SmolStr` from a statically allocated string.
93    ///
94    /// This never allocates.
95    #[inline(always)]
96    pub const fn new_static(text: &'static str) -> SmolStr {
97        // NOTE: this never uses the inline storage; if a canonical
98        // representation is needed, we could check for `len() < INLINE_CAP`
99        // and call `new_inline`, but this would mean an extra branch.
100        SmolStr(Repr::Static(text))
101    }
102
103    pub fn new<T>(text: T) -> SmolStr
104    where
105        T: AsRef<str>,
106    {
107        SmolStr(Repr::new(text))
108    }
109
110    #[inline(always)]
111    pub fn as_str(&self) -> &str {
112        self.0.as_str()
113    }
114
115    #[allow(clippy::inherent_to_string_shadow_display)]
116    #[inline(always)]
117    pub fn to_string(&self) -> String {
118        use alloc::borrow::ToOwned;
119
120        self.as_str().to_owned()
121    }
122
123    #[inline(always)]
124    pub fn len(&self) -> usize {
125        self.0.len()
126    }
127
128    #[inline(always)]
129    pub fn is_empty(&self) -> bool {
130        self.0.is_empty()
131    }
132
133    #[inline(always)]
134    pub const fn is_heap_allocated(&self) -> bool {
135        matches!(self.0, Repr::Heap(..))
136    }
137
138    fn from_char_iter<I: iter::Iterator<Item = char>>(mut iter: I) -> SmolStr {
139        let (min_size, _) = iter.size_hint();
140        if min_size > INLINE_CAP {
141            let heap: String = iter.collect();
142            return SmolStr(Repr::Heap(heap.into_boxed_str().into()));
143        }
144        let mut len = 0;
145        let mut buf = [0u8; INLINE_CAP];
146        while let Some(ch) = iter.next() {
147            let size = ch.len_utf8();
148            if size + len > INLINE_CAP {
149                let (min_remaining, _) = iter.size_hint();
150                let mut heap = String::with_capacity(size + len + min_remaining);
151                heap.push_str(core::str::from_utf8(&buf[..len]).unwrap());
152                heap.push(ch);
153                heap.extend(iter);
154                return SmolStr(Repr::Heap(heap.into_boxed_str().into()));
155            }
156            ch.encode_utf8(&mut buf[len..]);
157            len += size;
158        }
159        SmolStr(Repr::Inline {
160            // SAFETY: We know that `len` is less than or equal to the maximum value of `InlineSize`
161            // as we otherwise return early.
162            len: unsafe { InlineSize::transmute_from_u8(len as u8) },
163            buf,
164        })
165    }
166}
167
168impl Default for SmolStr {
169    #[inline(always)]
170    fn default() -> SmolStr {
171        SmolStr(Repr::Inline {
172            len: InlineSize::_V0,
173            buf: [0; INLINE_CAP],
174        })
175    }
176}
177
178impl Deref for SmolStr {
179    type Target = str;
180
181    #[inline(always)]
182    fn deref(&self) -> &str {
183        self.as_str()
184    }
185}
186
187impl PartialEq<SmolStr> for SmolStr {
188    fn eq(&self, other: &SmolStr) -> bool {
189        self.0.ptr_eq(&other.0) || self.as_str() == other.as_str()
190    }
191}
192
193impl Eq for SmolStr {}
194
195impl PartialEq<str> for SmolStr {
196    fn eq(&self, other: &str) -> bool {
197        self.as_str() == other
198    }
199}
200
201impl PartialEq<SmolStr> for str {
202    fn eq(&self, other: &SmolStr) -> bool {
203        other == self
204    }
205}
206
207impl<'a> PartialEq<&'a str> for SmolStr {
208    fn eq(&self, other: &&'a str) -> bool {
209        self == *other
210    }
211}
212
213impl<'a> PartialEq<SmolStr> for &'a str {
214    fn eq(&self, other: &SmolStr) -> bool {
215        *self == other
216    }
217}
218
219impl PartialEq<String> for SmolStr {
220    fn eq(&self, other: &String) -> bool {
221        self.as_str() == other
222    }
223}
224
225impl PartialEq<SmolStr> for String {
226    fn eq(&self, other: &SmolStr) -> bool {
227        other == self
228    }
229}
230
231impl<'a> PartialEq<&'a String> for SmolStr {
232    fn eq(&self, other: &&'a String) -> bool {
233        self == *other
234    }
235}
236
237impl<'a> PartialEq<SmolStr> for &'a String {
238    fn eq(&self, other: &SmolStr) -> bool {
239        *self == other
240    }
241}
242
243impl Ord for SmolStr {
244    fn cmp(&self, other: &SmolStr) -> Ordering {
245        self.as_str().cmp(other.as_str())
246    }
247}
248
249impl PartialOrd for SmolStr {
250    fn partial_cmp(&self, other: &SmolStr) -> Option<Ordering> {
251        Some(self.cmp(other))
252    }
253}
254
255impl hash::Hash for SmolStr {
256    fn hash<H: hash::Hasher>(&self, hasher: &mut H) {
257        self.as_str().hash(hasher);
258    }
259}
260
261impl fmt::Debug for SmolStr {
262    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
263        fmt::Debug::fmt(self.as_str(), f)
264    }
265}
266
267impl fmt::Display for SmolStr {
268    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
269        fmt::Display::fmt(self.as_str(), f)
270    }
271}
272
273impl iter::FromIterator<char> for SmolStr {
274    fn from_iter<I: iter::IntoIterator<Item = char>>(iter: I) -> SmolStr {
275        let iter = iter.into_iter();
276        Self::from_char_iter(iter)
277    }
278}
279
280fn build_from_str_iter<T>(mut iter: impl Iterator<Item = T>) -> SmolStr
281where
282    T: AsRef<str>,
283    String: iter::Extend<T>,
284{
285    let mut len = 0;
286    let mut buf = [0u8; INLINE_CAP];
287    while let Some(slice) = iter.next() {
288        let slice = slice.as_ref();
289        let size = slice.len();
290        if size + len > INLINE_CAP {
291            let mut heap = String::with_capacity(size + len);
292            heap.push_str(core::str::from_utf8(&buf[..len]).unwrap());
293            heap.push_str(slice);
294            heap.extend(iter);
295            return SmolStr(Repr::Heap(heap.into_boxed_str().into()));
296        }
297        buf[len..][..size].copy_from_slice(slice.as_bytes());
298        len += size;
299    }
300    SmolStr(Repr::Inline {
301        // SAFETY: We know that `len` is less than or equal to the maximum value of `InlineSize`
302        // as we otherwise return early.
303        len: unsafe { InlineSize::transmute_from_u8(len as u8) },
304        buf,
305    })
306}
307
308impl iter::FromIterator<String> for SmolStr {
309    fn from_iter<I: iter::IntoIterator<Item = String>>(iter: I) -> SmolStr {
310        build_from_str_iter(iter.into_iter())
311    }
312}
313
314impl<'a> iter::FromIterator<&'a String> for SmolStr {
315    fn from_iter<I: iter::IntoIterator<Item = &'a String>>(iter: I) -> SmolStr {
316        SmolStr::from_iter(iter.into_iter().map(|x| x.as_str()))
317    }
318}
319
320impl<'a> iter::FromIterator<&'a str> for SmolStr {
321    fn from_iter<I: iter::IntoIterator<Item = &'a str>>(iter: I) -> SmolStr {
322        build_from_str_iter(iter.into_iter())
323    }
324}
325
326impl AsRef<str> for SmolStr {
327    #[inline(always)]
328    fn as_ref(&self) -> &str {
329        self.as_str()
330    }
331}
332
333impl From<&str> for SmolStr {
334    #[inline]
335    fn from(s: &str) -> SmolStr {
336        SmolStr::new(s)
337    }
338}
339
340impl From<&mut str> for SmolStr {
341    #[inline]
342    fn from(s: &mut str) -> SmolStr {
343        SmolStr::new(s)
344    }
345}
346
347impl From<&String> for SmolStr {
348    #[inline]
349    fn from(s: &String) -> SmolStr {
350        SmolStr::new(s)
351    }
352}
353
354impl From<String> for SmolStr {
355    #[inline(always)]
356    fn from(text: String) -> Self {
357        Self::new(text)
358    }
359}
360
361impl From<Box<str>> for SmolStr {
362    #[inline]
363    fn from(s: Box<str>) -> SmolStr {
364        SmolStr::new(s)
365    }
366}
367
368impl From<Arc<str>> for SmolStr {
369    #[inline]
370    fn from(s: Arc<str>) -> SmolStr {
371        let repr = Repr::new_on_stack(s.as_ref()).unwrap_or_else(|| Repr::Heap(s));
372        Self(repr)
373    }
374}
375
376impl<'a> From<Cow<'a, str>> for SmolStr {
377    #[inline]
378    fn from(s: Cow<'a, str>) -> SmolStr {
379        SmolStr::new(s)
380    }
381}
382
383impl From<SmolStr> for Arc<str> {
384    #[inline(always)]
385    fn from(text: SmolStr) -> Self {
386        match text.0 {
387            Repr::Heap(data) => data,
388            _ => text.as_str().into(),
389        }
390    }
391}
392
393impl From<SmolStr> for String {
394    #[inline(always)]
395    fn from(text: SmolStr) -> Self {
396        text.as_str().into()
397    }
398}
399
400impl Borrow<str> for SmolStr {
401    #[inline(always)]
402    fn borrow(&self) -> &str {
403        self.as_str()
404    }
405}
406
407impl FromStr for SmolStr {
408    type Err = Infallible;
409
410    #[inline]
411    fn from_str(s: &str) -> Result<SmolStr, Self::Err> {
412        Ok(SmolStr::from(s))
413    }
414}
415
416#[cfg(feature = "arbitrary")]
417impl<'a> arbitrary::Arbitrary<'a> for SmolStr {
418    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> Result<Self, arbitrary::Error> {
419        let s = <&str>::arbitrary(u)?;
420        Ok(SmolStr::new(s))
421    }
422}
423
424const INLINE_CAP: usize = InlineSize::_V23 as usize;
425const N_NEWLINES: usize = 32;
426const N_SPACES: usize = 128;
427const WS: &str =
428    "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n                                                                                                                                ";
429const _: () = {
430    assert!(WS.len() == N_NEWLINES + N_SPACES);
431    assert!(WS.as_bytes()[N_NEWLINES - 1] == b'\n');
432    assert!(WS.as_bytes()[N_NEWLINES] == b' ');
433};
434
435#[derive(Clone, Copy, Debug, PartialEq)]
436#[repr(u8)]
437enum InlineSize {
438    _V0 = 0,
439    _V1,
440    _V2,
441    _V3,
442    _V4,
443    _V5,
444    _V6,
445    _V7,
446    _V8,
447    _V9,
448    _V10,
449    _V11,
450    _V12,
451    _V13,
452    _V14,
453    _V15,
454    _V16,
455    _V17,
456    _V18,
457    _V19,
458    _V20,
459    _V21,
460    _V22,
461    _V23,
462}
463
464impl InlineSize {
465    #[inline(always)]
466    const unsafe fn transmute_from_u8(value: u8) -> Self {
467        debug_assert!(value <= InlineSize::_V23 as u8);
468        unsafe { transmute::<u8, Self>(value) }
469    }
470}
471
472#[derive(Clone, Debug)]
473enum Repr {
474    Inline {
475        len: InlineSize,
476        buf: [u8; INLINE_CAP],
477    },
478    Static(&'static str),
479    Heap(Arc<str>),
480}
481
482impl Repr {
483    /// This function tries to create a new Repr::Inline or Repr::Static
484    /// If it isn't possible, this function returns None
485    fn new_on_stack<T>(text: T) -> Option<Self>
486    where
487        T: AsRef<str>,
488    {
489        let text = text.as_ref();
490
491        let len = text.len();
492        if len <= INLINE_CAP {
493            let mut buf = [0; INLINE_CAP];
494            buf[..len].copy_from_slice(text.as_bytes());
495            return Some(Repr::Inline {
496                // SAFETY: We know that `len` is less than or equal to the maximum value of `InlineSize`
497                len: unsafe { InlineSize::transmute_from_u8(len as u8) },
498                buf,
499            });
500        }
501
502        if len <= N_NEWLINES + N_SPACES {
503            let bytes = text.as_bytes();
504            let possible_newline_count = cmp::min(len, N_NEWLINES);
505            let newlines = bytes[..possible_newline_count]
506                .iter()
507                .take_while(|&&b| b == b'\n')
508                .count();
509            let possible_space_count = len - newlines;
510            if possible_space_count <= N_SPACES && bytes[newlines..].iter().all(|&b| b == b' ') {
511                let spaces = possible_space_count;
512                let substring = &WS[N_NEWLINES - newlines..N_NEWLINES + spaces];
513                return Some(Repr::Static(substring));
514            }
515        }
516        None
517    }
518
519    fn new<T>(text: T) -> Self
520    where
521        T: AsRef<str>,
522    {
523        Self::new_on_stack(text.as_ref()).unwrap_or_else(|| Repr::Heap(text.as_ref().into()))
524    }
525
526    #[inline(always)]
527    fn len(&self) -> usize {
528        match self {
529            Repr::Heap(data) => data.len(),
530            Repr::Static(data) => data.len(),
531            Repr::Inline { len, .. } => *len as usize,
532        }
533    }
534
535    #[inline(always)]
536    fn is_empty(&self) -> bool {
537        match self {
538            Repr::Heap(data) => data.is_empty(),
539            Repr::Static(data) => data.is_empty(),
540            Repr::Inline { len, .. } => *len as u8 == 0,
541        }
542    }
543
544    #[inline]
545    fn as_str(&self) -> &str {
546        match self {
547            Repr::Heap(data) => data,
548            Repr::Static(data) => data,
549            Repr::Inline { len, buf } => {
550                let len = *len as usize;
551                let buf = &buf[..len];
552                // SAFETY: buf is guaranteed to be valid utf8 for ..len bytes
553                unsafe { ::core::str::from_utf8_unchecked(buf) }
554            }
555        }
556    }
557
558    fn ptr_eq(&self, other: &Self) -> bool {
559        match (self, other) {
560            (Self::Heap(l0), Self::Heap(r0)) => Arc::ptr_eq(l0, r0),
561            (Self::Static(l0), Self::Static(r0)) => core::ptr::eq(l0, r0),
562            (
563                Self::Inline {
564                    len: l_len,
565                    buf: l_buf,
566                },
567                Self::Inline {
568                    len: r_len,
569                    buf: r_buf,
570                },
571            ) => l_len == r_len && l_buf == r_buf,
572            _ => false,
573        }
574    }
575}
576
577/// Convert value to [`SmolStr`] using [`fmt::Display`], potentially without allocating.
578///
579/// Almost identical to [`ToString`], but converts to `SmolStr` instead.
580pub trait ToSmolStr {
581    fn to_smolstr(&self) -> SmolStr;
582}
583
584/// [`str`] methods producing [`SmolStr`]s.
585pub trait StrExt: private::Sealed {
586    /// Returns the lowercase equivalent of this string slice as a new [`SmolStr`],
587    /// potentially without allocating.
588    ///
589    /// See [`str::to_lowercase`].
590    #[must_use = "this returns a new SmolStr without modifying the original"]
591    fn to_lowercase_smolstr(&self) -> SmolStr;
592
593    /// Returns the uppercase equivalent of this string slice as a new [`SmolStr`],
594    /// potentially without allocating.
595    ///
596    /// See [`str::to_uppercase`].
597    #[must_use = "this returns a new SmolStr without modifying the original"]
598    fn to_uppercase_smolstr(&self) -> SmolStr;
599
600    /// Returns the ASCII lowercase equivalent of this string slice as a new [`SmolStr`],
601    /// potentially without allocating.
602    ///
603    /// See [`str::to_ascii_lowercase`].
604    #[must_use = "this returns a new SmolStr without modifying the original"]
605    fn to_ascii_lowercase_smolstr(&self) -> SmolStr;
606
607    /// Returns the ASCII uppercase equivalent of this string slice as a new [`SmolStr`],
608    /// potentially without allocating.
609    ///
610    /// See [`str::to_ascii_uppercase`].
611    #[must_use = "this returns a new SmolStr without modifying the original"]
612    fn to_ascii_uppercase_smolstr(&self) -> SmolStr;
613
614    /// Replaces all matches of a &str with another &str returning a new [`SmolStr`],
615    /// potentially without allocating.
616    ///
617    /// See [`str::replace`].
618    // TODO: Use `Pattern` when stable.
619    #[must_use = "this returns a new SmolStr without modifying the original"]
620    fn replace_smolstr(&self, from: &str, to: &str) -> SmolStr;
621
622    /// Replaces first N matches of a &str with another &str returning a new [`SmolStr`],
623    /// potentially without allocating.
624    ///
625    /// See [`str::replacen`].
626    // TODO: Use `Pattern` when stable.
627    #[must_use = "this returns a new SmolStr without modifying the original"]
628    fn replacen_smolstr(&self, from: &str, to: &str, count: usize) -> SmolStr;
629}
630
631impl StrExt for str {
632    #[inline]
633    fn to_lowercase_smolstr(&self) -> SmolStr {
634        SmolStr::from_char_iter(self.chars().flat_map(|c| c.to_lowercase()))
635    }
636
637    #[inline]
638    fn to_uppercase_smolstr(&self) -> SmolStr {
639        SmolStr::from_char_iter(self.chars().flat_map(|c| c.to_uppercase()))
640    }
641
642    #[inline]
643    fn to_ascii_lowercase_smolstr(&self) -> SmolStr {
644        SmolStr::from_char_iter(self.chars().map(|c| c.to_ascii_lowercase()))
645    }
646
647    #[inline]
648    fn to_ascii_uppercase_smolstr(&self) -> SmolStr {
649        SmolStr::from_char_iter(self.chars().map(|c| c.to_ascii_uppercase()))
650    }
651
652    #[inline]
653    fn replace_smolstr(&self, from: &str, to: &str) -> SmolStr {
654        self.replacen_smolstr(from, to, usize::MAX)
655    }
656
657    #[inline]
658    fn replacen_smolstr(&self, from: &str, to: &str, count: usize) -> SmolStr {
659        let mut result = Writer::new();
660        let mut last_end = 0;
661        for (start, part) in self.match_indices(from).take(count) {
662            // SAFETY: `start` is guaranteed to be within the bounds of `self` as per
663            // `match_indices` and last_end is always less than or equal to `start`
664            result.push_str(unsafe { self.get_unchecked(last_end..start) });
665            result.push_str(to);
666            last_end = start + part.len();
667        }
668        // SAFETY: `self.len()` is guaranteed to be within the bounds of `self` and last_end is
669        // always less than or equal to `self.len()`
670        result.push_str(unsafe { self.get_unchecked(last_end..self.len()) });
671        SmolStr::from(result)
672    }
673}
674
675mod private {
676    /// No downstream impls allowed.
677    pub trait Sealed {}
678    impl Sealed for str {}
679}
680
681/// Formats arguments to a [`SmolStr`], potentially without allocating.
682///
683/// See [`alloc::format!`] or [`format_args!`] for syntax documentation.
684#[macro_export]
685macro_rules! format_smolstr {
686    ($($tt:tt)*) => {{
687        use ::core::fmt::Write;
688        let mut w = $crate::Writer::new();
689        w.write_fmt(format_args!($($tt)*)).expect("a formatting trait implementation returned an error");
690        $crate::SmolStr::from(w)
691    }};
692}
693
694#[doc(hidden)]
695pub struct Writer {
696    inline: [u8; INLINE_CAP],
697    heap: String,
698    len: usize,
699}
700
701impl Writer {
702    #[must_use]
703    pub const fn new() -> Self {
704        Writer {
705            inline: [0; INLINE_CAP],
706            heap: String::new(),
707            len: 0,
708        }
709    }
710
711    fn push_str(&mut self, s: &str) {
712        // if currently on the stack
713        if self.len <= INLINE_CAP {
714            let old_len = self.len;
715            self.len += s.len();
716
717            // if the new length will fit on the stack (even if it fills it entirely)
718            if self.len <= INLINE_CAP {
719                self.inline[old_len..self.len].copy_from_slice(s.as_bytes());
720                return; // skip the heap push below
721            }
722
723            self.heap.reserve(self.len);
724
725            // copy existing inline bytes over to the heap
726            // SAFETY: inline data is guaranteed to be valid utf8 for `old_len` bytes
727            unsafe {
728                self.heap
729                    .as_mut_vec()
730                    .extend_from_slice(&self.inline[..old_len]);
731            }
732        }
733
734        self.heap.push_str(s);
735    }
736}
737
738impl fmt::Write for Writer {
739    #[inline]
740    fn write_str(&mut self, s: &str) -> fmt::Result {
741        self.push_str(s);
742        Ok(())
743    }
744}
745
746impl From<Writer> for SmolStr {
747    fn from(value: Writer) -> Self {
748        SmolStr(if value.len <= INLINE_CAP {
749            Repr::Inline {
750                // SAFETY: We know that `value.len` is less than or equal to the maximum value of `InlineSize`
751                len: unsafe { InlineSize::transmute_from_u8(value.len as u8) },
752                buf: value.inline,
753            }
754        } else {
755            Repr::new(value.heap)
756        })
757    }
758}
759
760impl<T> ToSmolStr for T
761where
762    T: fmt::Display + ?Sized,
763{
764    fn to_smolstr(&self) -> SmolStr {
765        format_smolstr!("{}", self)
766    }
767}
768
769#[cfg(feature = "serde")]
770mod serde {
771    use alloc::{string::String, vec::Vec};
772    use core::fmt;
773
774    use serde::de::{Deserializer, Error, Unexpected, Visitor};
775
776    use crate::SmolStr;
777
778    // https://github.com/serde-rs/serde/blob/629802f2abfd1a54a6072992888fea7ca5bc209f/serde/src/private/de.rs#L56-L125
779    fn smol_str<'de: 'a, 'a, D>(deserializer: D) -> Result<SmolStr, D::Error>
780    where
781        D: Deserializer<'de>,
782    {
783        struct SmolStrVisitor;
784
785        impl<'a> Visitor<'a> for SmolStrVisitor {
786            type Value = SmolStr;
787
788            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
789                formatter.write_str("a string")
790            }
791
792            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
793            where
794                E: Error,
795            {
796                Ok(SmolStr::from(v))
797            }
798
799            fn visit_borrowed_str<E>(self, v: &'a str) -> Result<Self::Value, E>
800            where
801                E: Error,
802            {
803                Ok(SmolStr::from(v))
804            }
805
806            fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
807            where
808                E: Error,
809            {
810                Ok(SmolStr::from(v))
811            }
812
813            fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
814            where
815                E: Error,
816            {
817                match core::str::from_utf8(v) {
818                    Ok(s) => Ok(SmolStr::from(s)),
819                    Err(_) => Err(Error::invalid_value(Unexpected::Bytes(v), &self)),
820                }
821            }
822
823            fn visit_borrowed_bytes<E>(self, v: &'a [u8]) -> Result<Self::Value, E>
824            where
825                E: Error,
826            {
827                match core::str::from_utf8(v) {
828                    Ok(s) => Ok(SmolStr::from(s)),
829                    Err(_) => Err(Error::invalid_value(Unexpected::Bytes(v), &self)),
830                }
831            }
832
833            fn visit_byte_buf<E>(self, v: Vec<u8>) -> Result<Self::Value, E>
834            where
835                E: Error,
836            {
837                match String::from_utf8(v) {
838                    Ok(s) => Ok(SmolStr::from(s)),
839                    Err(e) => Err(Error::invalid_value(
840                        Unexpected::Bytes(&e.into_bytes()),
841                        &self,
842                    )),
843                }
844            }
845        }
846
847        deserializer.deserialize_str(SmolStrVisitor)
848    }
849
850    impl serde::Serialize for SmolStr {
851        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
852        where
853            S: serde::Serializer,
854        {
855            self.as_str().serialize(serializer)
856        }
857    }
858
859    impl<'de> serde::Deserialize<'de> for SmolStr {
860        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
861        where
862            D: serde::Deserializer<'de>,
863        {
864            smol_str(deserializer)
865        }
866    }
867}