Skip to main content

small_collections/
small_string.rs

1extern crate alloc;
2
3use alloc::{string::String, vec::Vec};
4use core::str::FromStr;
5
6/// A string that stores up to `N` bytes inline (on the stack) and spills onto
7/// the heap once it grows past that inline capacity.
8///
9/// `SmallString` behaves like [`alloc::string::String`] for the operations it
10/// exposes: it dereferences to `str`, so all string methods are available. The
11/// only difference is *where* short strings are stored: values whose UTF-8
12/// length fits in `N` bytes never touch the allocator.
13///
14/// # Examples
15///
16/// ```
17/// use small_collections::SmallString;
18///
19/// let mut s: SmallString<8> = SmallString::new();
20/// s.push_str("hello");
21/// assert!(s.is_inline());
22/// assert_eq!(&*s, "hello");
23///
24/// // Pushing past `N` bytes automatically spills onto the heap.
25/// s.push_str(" world");
26/// assert!(!s.is_inline());
27/// assert_eq!(s.as_str(), "hello world");
28/// ```
29///
30/// # Invariants
31///
32/// * The `Inline` variant never holds more than `N` bytes.
33/// * A value only ever moves from `Inline` to `Heap` on growth; it moves back
34///   to `Inline` only through [`SmallString::shrink_to_fit`] or
35///   [`SmallString::shrink_to`]. [`SmallString::clear`] and
36///   [`SmallString::truncate`] keep the current storage.
37///
38/// # Serialization
39///
40/// With the `serde` feature enabled, a `SmallString` serializes exactly like a
41/// `str` (for example the JSON string `"hello"`), regardless of whether its
42/// contents are stored inline or on the heap. This matches the flat sequence
43/// representation used by [`SmallVec`](crate::SmallVec).
44#[derive(Clone)]
45pub enum SmallString<const N: usize> {
46    /// Data stored inline, on the stack, with a fixed capacity of `N` bytes.
47    Inline(heapless::String<N>),
48    /// Data spilled onto the heap, growing as needed.
49    Heap(alloc::string::String),
50}
51
52impl<const N: usize> SmallString<N> {
53    /// Creates a new, empty `SmallString` using its inline storage.
54    ///
55    /// This never allocates.
56    #[inline]
57    pub fn new() -> Self {
58        SmallString::Inline(heapless::String::new())
59    }
60
61    /// Returns the inline capacity, `N`.
62    #[inline]
63    pub const fn inline_size() -> usize {
64        N
65    }
66
67    /// Copies the contents of an `&str` into a new [`SmallString`].
68    ///
69    /// The data is stored inline when its UTF-8 length is at most `N`,
70    /// otherwise it is copied onto the heap.
71    ///
72    /// # Examples
73    ///
74    /// ```
75    /// use small_collections::SmallString;
76    ///
77    /// let inline: SmallString<8> = SmallString::from_str("hello");
78    /// assert!(inline.is_inline());
79    ///
80    /// let heap: SmallString<2> = SmallString::from_str("hello");
81    /// assert!(!heap.is_inline());
82    /// ```
83    // Kept as an infallible inherent constructor alongside the `FromStr`
84    // impl; the name mirrors std's `String`-like constructors.
85    #[allow(clippy::should_implement_trait)]
86    #[inline]
87    pub fn from_str(s: &str) -> Self {
88        let mut out = Self::new();
89        out.push_str(s);
90        out
91    }
92
93    /// Creates a [`SmallString`] from an already heap-allocated `String`.
94    ///
95    /// The existing allocation is preserved, so the result always uses the
96    /// `Heap` variant, except for an empty `String` with no capacity, which
97    /// returns an inline `SmallString` (matching
98    /// [`SmallVec::from_vec`](crate::SmallVec::from_vec)). Use
99    /// [`SmallString::from_str`] if you want a short string to move inline.
100    #[inline]
101    pub fn from_string(s: String) -> Self {
102        if s.capacity() == 0 { Self::new() } else { Self::Heap(s) }
103    }
104
105    /// Moves the contents of a `SmallString` with a different inline capacity
106    /// into this one.
107    ///
108    /// The bytes are stored inline when they fit in `N`, otherwise they are
109    /// spilled onto the heap. Unlike [`SmallString::from_string`], this does
110    /// not preserve an existing heap allocation when the contents would fit
111    /// inline.
112    #[inline]
113    pub fn from_small_string<const M: usize>(input: SmallString<M>) -> Self {
114        match input {
115            SmallString::Heap(s) if s.len() <= N => SmallString::from_str(&s),
116            SmallString::Heap(s) => SmallString::Heap(s),
117            SmallString::Inline(s) => SmallString::from_str(s.as_str()),
118        }
119    }
120
121    /// Converts a `Vec` of bytes to a [`SmallString`].
122    ///
123    /// The bytes are stored inline when they fit in `N`, otherwise they are
124    /// moved onto the heap.
125    ///
126    /// # Errors
127    ///
128    /// Returns an error if the bytes are not valid UTF-8.
129    #[inline]
130    pub fn from_utf8(bytes: Vec<u8>) -> Result<Self, alloc::string::FromUtf8Error> {
131        let s = String::from_utf8(bytes)?;
132        Ok(if s.len() <= N {
133            Self::from_str(&s)
134        } else {
135            Self::from_string(s)
136        })
137    }
138
139    /// Converts a slice of bytes to a [`SmallString`].
140    ///
141    /// The bytes are stored inline when they fit in `N`, otherwise they are
142    /// copied onto the heap.
143    ///
144    /// # Errors
145    ///
146    /// Returns an error if the bytes are not valid UTF-8.
147    #[inline]
148    pub fn from_utf8_slice(bytes: &[u8]) -> Result<Self, core::str::Utf8Error> {
149        Ok(Self::from_str(core::str::from_utf8(bytes)?))
150    }
151
152    /// Converts a slice of bytes to a [`SmallString`], replacing invalid
153    /// sequences with U+FFFD.
154    ///
155    /// The bytes are streamed directly into the inline storage, so nothing is
156    /// allocated unless the (lossily converted) contents exceed the `N`-byte
157    /// inline capacity, in which case they spill onto the heap. This holds even
158    /// when replacements are needed: no intermediate [`String`] is created.
159    #[inline]
160    pub fn from_utf8_lossy(bytes: &[u8]) -> Self {
161        let mut out = Self::new();
162        for chunk in bytes.utf8_chunks() {
163            out.push_str(chunk.valid());
164            if !chunk.invalid().is_empty() {
165                out.push('\u{FFFD}');
166            }
167        }
168        out
169    }
170
171    /// Returns `true` if the string is currently storing data inline (on the
172    /// stack).
173    #[inline]
174    pub fn is_inline(&self) -> bool {
175        matches!(self, SmallString::Inline(_))
176    }
177
178    /// Returns the total capacity (in bytes) of the string.
179    ///
180    /// For an inline string this is always `N`; for a spilled string it is the
181    /// current heap capacity.
182    #[inline]
183    pub fn capacity(&self) -> usize {
184        match self {
185            SmallString::Inline(_) => N,
186            SmallString::Heap(str) => str.capacity(),
187        }
188    }
189
190    /// Returns the length of this [`SmallString`] in bytes, not [`char`]s or
191    /// graphemes.
192    #[inline]
193    pub fn len(&self) -> usize {
194        match self {
195            SmallString::Inline(str) => str.len(),
196            SmallString::Heap(str) => str.len(),
197        }
198    }
199
200    /// Returns `true` if this [`SmallString`] has a length of zero.
201    #[inline]
202    pub fn is_empty(&self) -> bool {
203        self.len() == 0
204    }
205
206    /// Truncates this [`SmallString`], removing all contents.
207    ///
208    /// While this means the [`SmallString`] will have a length of zero, it
209    /// does not touch its capacity or change its storage (inline stays inline,
210    /// heap stays heap).
211    #[inline]
212    pub fn clear(&mut self) {
213        match self {
214            SmallString::Inline(str) => str.clear(),
215            SmallString::Heap(str) => str.clear(),
216        }
217    }
218
219    /// Extracts a string slice containing the entire `SmallString`.
220    #[inline]
221    pub fn as_str(&self) -> &str {
222        // Leverage Deref
223        self
224    }
225
226    /// Extracts a mutable string slice containing the entire `SmallString`.
227    ///
228    /// Mutating the slice must preserve valid UTF-8; methods such as
229    /// [`str::make_ascii_uppercase`] do.
230    #[inline]
231    pub fn as_mut_str(&mut self) -> &mut str {
232        // Leverage DerefMut
233        self
234    }
235
236    /// Converts a [`SmallString`] into a byte slice.
237    #[inline]
238    pub fn as_bytes(&self) -> &[u8] {
239        self.as_str().as_bytes()
240    }
241
242    /// Returns a mutable byte slice over the contents.
243    ///
244    /// # Safety
245    ///
246    /// The caller must ensure that the content of the slice remains valid UTF-8.
247    /// If this invariant is violated, it is Undefined Behavior.
248    #[inline]
249    pub unsafe fn as_bytes_mut(&mut self) -> &mut [u8] {
250        unsafe { self.as_mut_str().as_bytes_mut() }
251    }
252
253    /// Removes the last character from the string buffer and returns it.
254    /// Returns [`None`] if the string is empty.
255    #[inline]
256    pub fn pop(&mut self) -> Option<char> {
257        match self {
258            SmallString::Inline(str) => str.pop(),
259            SmallString::Heap(str) => str.pop(),
260        }
261    }
262
263    /// Shortens this string to the specified length.
264    ///
265    /// If `new_len` is greater than or equal to the current length, this does
266    /// nothing. Keeps the current storage.
267    ///
268    /// # Panics
269    ///
270    /// Panics if `new_len` is less than the current length and does not lie on
271    /// a [`char`] boundary.
272    #[inline]
273    pub fn truncate(&mut self, new_len: usize) {
274        match self {
275            SmallString::Inline(str) => str.truncate(new_len),
276            SmallString::Heap(str) => str.truncate(new_len),
277        }
278    }
279
280    /// Appends a string slice to the end of this string, spilling onto the
281    /// heap if the inline capacity is exceeded.
282    #[inline]
283    pub fn push_str(&mut self, input: &str) {
284        match self {
285            SmallString::Heap(s) => s.push_str(input),
286            SmallString::Inline(s) => {
287                // `s.len() <= N`, so this subtraction cannot underflow.
288                if input.len() <= N - s.len() {
289                    // guaranteed to succeed
290                    let _ = s.push_str(input);
291                } else {
292                    // we need to spill on the heap
293                    let new_capacity = s.len().saturating_add(input.len()).max(N.saturating_mul(2));
294                    let mut heap = String::with_capacity(new_capacity);
295                    heap.push_str(s.as_str());
296                    heap.push_str(input);
297                    *self = SmallString::Heap(heap);
298                }
299            }
300        }
301    }
302
303    /// Appends a character to the end of this string, spilling onto the heap
304    /// if the inline capacity is exceeded.
305    #[inline]
306    pub fn push(&mut self, ch: char) {
307        match self {
308            SmallString::Heap(s) => s.push(ch),
309            SmallString::Inline(s) => {
310                let char_len = ch.len_utf8();
311                // `s.len() <= N`, so this subtraction cannot underflow.
312                if char_len <= N - s.len() {
313                    // guaranteed to succeed
314                    let _ = s.push(ch);
315                } else {
316                    // we need to spill on the heap
317                    let new_capacity = s.len().saturating_add(char_len).max(N.saturating_mul(2));
318                    let mut heap = String::with_capacity(new_capacity);
319                    heap.push_str(s.as_str());
320                    heap.push(ch);
321                    *self = SmallString::Heap(heap);
322                }
323            }
324        }
325    }
326
327    /// Reserves capacity for at least `additional` more bytes, spilling onto
328    /// the heap if needed.
329    ///
330    /// After this call, [`capacity`](SmallString::capacity) is at least
331    /// `len() + additional`.
332    ///
333    /// # Panics
334    ///
335    /// Panics if the new capacity overflows `usize` or the allocator reports a
336    /// failure.
337    #[inline]
338    pub fn reserve(&mut self, additional: usize) {
339        match self {
340            SmallString::Heap(s) => s.reserve(additional),
341            SmallString::Inline(s) => {
342                if additional > N - s.len() {
343                    // spill to heap
344                    let new_capacity = s.len().saturating_add(additional).max(N.saturating_mul(2));
345                    let mut heap = String::with_capacity(new_capacity);
346                    heap.push_str(s);
347                    *self = SmallString::Heap(heap);
348                }
349            }
350        }
351    }
352
353    /// Reserves capacity for exactly `additional` more bytes, spilling onto the
354    /// heap if needed.
355    ///
356    /// Prefer [`SmallString::reserve`] when `additional` is only an estimate.
357    ///
358    /// # Panics
359    ///
360    /// Panics if the new capacity overflows `usize` or the allocator reports a
361    /// failure.
362    #[inline]
363    pub fn reserve_exact(&mut self, additional: usize) {
364        match self {
365            SmallString::Heap(s) => s.reserve_exact(additional),
366            SmallString::Inline(s) => {
367                if additional > N - s.len() {
368                    // spill to heap
369                    let new_capacity = s.len().saturating_add(additional).max(N.saturating_add(1));
370                    let mut heap = String::with_capacity(new_capacity);
371                    heap.push_str(s);
372                    *self = SmallString::Heap(heap);
373                }
374            }
375        }
376    }
377
378    /// Shrinks the string as much as possible: an inline string is unchanged,
379    /// while a spilled string whose contents fit in `N` bytes moves back
380    /// inline.
381    #[inline]
382    pub fn shrink_to_fit(&mut self) {
383        if let SmallString::Heap(s) = self {
384            if s.len() <= N {
385                *self = SmallString::from_str(s.as_str());
386            } else {
387                s.shrink_to_fit();
388            }
389        }
390    }
391
392    /// Shrinks the string's capacity to at least `min_capacity` bytes, moving
393    /// it back inline when the result fits in `N`.
394    ///
395    /// Does nothing when the current capacity is already at or below
396    /// `min_capacity`.
397    #[inline]
398    pub fn shrink_to(&mut self, min_capacity: usize) {
399        if let SmallString::Heap(s) = self
400            && s.capacity() > min_capacity
401        {
402            let target = s.len().max(min_capacity);
403            if target <= N {
404                *self = SmallString::from_str(s.as_str());
405            } else {
406                s.shrink_to(target);
407            }
408        }
409    }
410
411    /// Converts this `SmallString` into an [`alloc::string::String`], reusing
412    /// the heap allocation when spilled.
413    ///
414    /// # Examples
415    ///
416    /// ```
417    /// use small_collections::SmallString;
418    ///
419    /// let s: SmallString<8> = SmallString::from_str("hello");
420    /// assert_eq!(s.into_string(), "hello");
421    /// ```
422    #[inline]
423    pub fn into_string(self) -> String {
424        match self {
425            SmallString::Inline(s) => String::from(s.as_str()),
426            SmallString::Heap(s) => s,
427        }
428    }
429
430    /// Converts this `SmallString` into a byte vector, reusing the heap
431    /// allocation when spilled.
432    #[inline]
433    pub fn into_bytes(self) -> Vec<u8> {
434        self.into_string().into_bytes()
435    }
436}
437
438impl<const N: usize> From<&str> for SmallString<N> {
439    #[inline]
440    fn from(s: &str) -> Self {
441        Self::from_str(s)
442    }
443}
444
445impl<const N: usize> From<alloc::string::String> for SmallString<N> {
446    #[inline]
447    fn from(s: alloc::string::String) -> Self {
448        Self::from_string(s)
449    }
450}
451
452impl<const N: usize> From<SmallString<N>> for alloc::string::String {
453    #[inline]
454    fn from(this: SmallString<N>) -> Self {
455        this.into_string()
456    }
457}
458
459impl<const N: usize> core::ops::Deref for SmallString<N> {
460    type Target = str;
461
462    #[inline]
463    fn deref(&self) -> &Self::Target {
464        match self {
465            SmallString::Inline(str) => str.as_str(),
466            SmallString::Heap(str) => str.as_str(),
467        }
468    }
469}
470
471impl<const N: usize> core::ops::DerefMut for SmallString<N> {
472    #[inline]
473    fn deref_mut(&mut self) -> &mut Self::Target {
474        match self {
475            SmallString::Inline(str) => str.as_mut_str(),
476            SmallString::Heap(str) => str.as_mut_str(),
477        }
478    }
479}
480
481impl<const N: usize> Default for SmallString<N> {
482    #[inline]
483    fn default() -> Self {
484        Self::new()
485    }
486}
487
488impl<const N: usize> core::fmt::Display for SmallString<N> {
489    #[inline]
490    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
491        core::fmt::Display::fmt(self.as_str(), f) // Delegate to str implementation
492    }
493}
494
495impl<const N: usize> core::fmt::Debug for SmallString<N> {
496    #[inline]
497    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
498        // Delegate to `str`'s `Debug` so the output is a quoted string,
499        // regardless of the underlying storage.
500        core::fmt::Debug::fmt(self.as_str(), f)
501    }
502}
503
504impl<const N: usize> core::fmt::Write for SmallString<N> {
505    #[inline]
506    fn write_str(&mut self, s: &str) -> core::fmt::Result {
507        self.push_str(s);
508        Ok(())
509    }
510}
511
512impl<const N: usize, const M: usize> PartialEq<SmallString<M>> for SmallString<N> {
513    #[inline]
514    fn eq(&self, other: &SmallString<M>) -> bool {
515        self.as_str() == other.as_str()
516    }
517}
518
519impl<const N: usize> Eq for SmallString<N> {}
520
521impl<const N: usize> PartialEq<str> for SmallString<N> {
522    #[inline]
523    fn eq(&self, other: &str) -> bool {
524        self.as_str() == other
525    }
526}
527
528impl<'a, const N: usize> PartialEq<&'a str> for SmallString<N> {
529    #[inline]
530    fn eq(&self, other: &&'a str) -> bool {
531        self.as_str() == *other
532    }
533}
534
535impl<const N: usize> PartialEq<SmallString<N>> for &str {
536    #[inline]
537    fn eq(&self, other: &SmallString<N>) -> bool {
538        *self == other.as_str()
539    }
540}
541
542impl<const N: usize> PartialEq<alloc::string::String> for SmallString<N> {
543    #[inline]
544    fn eq(&self, other: &alloc::string::String) -> bool {
545        self.as_str() == other.as_str()
546    }
547}
548
549impl<const N: usize> PartialEq<SmallString<N>> for alloc::string::String {
550    #[inline]
551    fn eq(&self, other: &SmallString<N>) -> bool {
552        self.as_str() == other.as_str()
553    }
554}
555
556impl<const N: usize> PartialEq<SmallString<N>> for str {
557    #[inline]
558    fn eq(&self, other: &SmallString<N>) -> bool {
559        self == other.as_str()
560    }
561}
562
563impl<const N: usize, const M: usize> PartialOrd<SmallString<M>> for SmallString<N> {
564    #[inline]
565    fn partial_cmp(&self, other: &SmallString<M>) -> Option<core::cmp::Ordering> {
566        Some(self.as_str().cmp(other.as_str()))
567    }
568}
569
570impl<const N: usize> Ord for SmallString<N> {
571    #[inline]
572    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
573        self.as_str().cmp(other.as_str())
574    }
575}
576
577impl<const N: usize> core::hash::Hash for SmallString<N> {
578    #[inline]
579    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
580        self.as_str().hash(state);
581    }
582}
583
584impl<const N: usize> core::borrow::Borrow<str> for SmallString<N> {
585    fn borrow(&self) -> &str {
586        self.as_str()
587    }
588}
589
590impl<const N: usize> core::borrow::BorrowMut<str> for SmallString<N> {
591    #[inline]
592    fn borrow_mut(&mut self) -> &mut str {
593        self.as_mut_str()
594    }
595}
596
597impl<const N: usize> AsRef<str> for SmallString<N> {
598    fn as_ref(&self) -> &str {
599        self.as_str()
600    }
601}
602
603impl<const N: usize> AsRef<[u8]> for SmallString<N> {
604    #[inline]
605    fn as_ref(&self) -> &[u8] {
606        self.as_bytes()
607    }
608}
609
610impl<const N: usize> AsMut<str> for SmallString<N> {
611    #[inline]
612    fn as_mut(&mut self) -> &mut str {
613        self.as_mut_str()
614    }
615}
616
617impl<const N: usize> FromIterator<char> for SmallString<N> {
618    #[inline]
619    fn from_iter<I: IntoIterator<Item = char>>(iter: I) -> Self {
620        let mut s = Self::new();
621        let iter = iter.into_iter();
622        s.reserve(iter.size_hint().0);
623        for c in iter {
624            s.push(c);
625        }
626        s
627    }
628}
629
630impl<'a, const N: usize> FromIterator<&'a str> for SmallString<N> {
631    #[inline]
632    fn from_iter<I: IntoIterator<Item = &'a str>>(iter: I) -> Self {
633        let mut s = Self::new();
634        let iter = iter.into_iter();
635        s.reserve(iter.size_hint().0);
636        for str_slice in iter {
637            s.push_str(str_slice);
638        }
639        s
640    }
641}
642
643impl<const N: usize> Extend<char> for SmallString<N> {
644    #[inline]
645    fn extend<I: IntoIterator<Item = char>>(&mut self, iter: I) {
646        let iter = iter.into_iter();
647        self.reserve(iter.size_hint().0);
648        for c in iter {
649            self.push(c);
650        }
651    }
652}
653
654impl<'a, const N: usize> Extend<&'a str> for SmallString<N> {
655    #[inline]
656    fn extend<I: IntoIterator<Item = &'a str>>(&mut self, iter: I) {
657        let iter = iter.into_iter();
658        self.reserve(iter.size_hint().0);
659        for str_slice in iter {
660            self.push_str(str_slice);
661        }
662    }
663}
664
665impl<const N: usize> FromStr for SmallString<N> {
666    type Err = core::convert::Infallible;
667
668    #[inline]
669    fn from_str(s: &str) -> Result<Self, Self::Err> {
670        let mut out = Self::new();
671        out.push_str(s);
672        Ok(out)
673    }
674}
675
676#[cfg(feature = "serde")]
677impl<const N: usize> serde::Serialize for SmallString<N> {
678    #[inline]
679    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
680        serializer.serialize_str(self.as_str())
681    }
682}
683
684#[cfg(feature = "serde")]
685impl<'de, const N: usize> serde::Deserialize<'de> for SmallString<N> {
686    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
687        struct SmallStringVisitor<const N: usize>;
688
689        impl<'de, const N: usize> serde::de::Visitor<'de> for SmallStringVisitor<N> {
690            type Value = SmallString<N>;
691
692            fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
693                formatter.write_str("a string")
694            }
695
696            fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
697                Ok(SmallString::from_str(v))
698            }
699
700            fn visit_borrowed_str<E: serde::de::Error>(self, v: &'de str) -> Result<Self::Value, E> {
701                Ok(SmallString::from_str(v))
702            }
703
704            fn visit_string<E: serde::de::Error>(self, v: String) -> Result<Self::Value, E> {
705                Ok(if v.len() <= N {
706                    SmallString::from_str(&v)
707                } else {
708                    SmallString::from_string(v)
709                })
710            }
711        }
712
713        deserializer.deserialize_str(SmallStringVisitor::<N>)
714    }
715}
716
717#[cfg(test)]
718mod tests {
719    use core::fmt::Write;
720
721    use crate::SmallString;
722
723    // -----------------------------------------------------------------------
724    // Existing baseline tests
725    // -----------------------------------------------------------------------
726
727    #[test]
728    fn test_basic_inline() {
729        let mut s: SmallString<16> = SmallString::new();
730        assert!(s.is_inline());
731        assert!(s.is_empty());
732        assert!(s.capacity() == 16);
733
734        s.push_str("Hello,");
735        assert_eq!(s.len(), 6);
736        assert_eq!(s.as_str(), "Hello,");
737        assert!(s.is_inline());
738        assert!(s.capacity() == 16);
739
740        s.push(' ');
741        s.push_str("World");
742        assert_eq!(s.len(), 12);
743        assert_eq!(&*s, "Hello, World");
744        assert!(s.is_inline());
745        assert!(s.capacity() == 16);
746    }
747
748    #[test]
749    fn test_basic_spill_to_heap() {
750        let mut s: SmallString<16> = SmallString::new();
751        assert!(s.is_inline());
752        assert!(s.is_empty());
753        assert!(s.capacity() == 16);
754
755        s.push_str("Hello, ");
756        assert_eq!(s.len(), 7);
757        assert_eq!(s.as_str(), "Hello, ");
758        assert!(s.is_inline());
759        assert!(s.capacity() == 16);
760
761        s.push_str(&"a".repeat(30));
762        assert_eq!(s.len(), 37);
763        assert_eq!(&s, &format!("Hello, {}", "a".repeat(30)));
764        assert!(!s.is_inline());
765        assert!(s.capacity() >= 37);
766    }
767
768    // -----------------------------------------------------------------------
769    // Construction methods
770    // -----------------------------------------------------------------------
771
772    #[test]
773    fn test_new_is_empty_and_default() {
774        let s: SmallString<8> = SmallString::new();
775        assert!(s.is_empty());
776        assert!(s.is_inline());
777        let s2: SmallString<8> = Default::default();
778        assert!(s2.is_empty());
779        assert!(s2.is_inline());
780    }
781
782    #[test]
783    fn test_from_str_inline() {
784        let s: SmallString<32> = SmallString::from_str("hello");
785        assert!(s.is_inline());
786        assert_eq!(s.as_str(), "hello");
787    }
788
789    #[test]
790    fn test_from_str_spill() {
791        let s: SmallString<4> = SmallString::from_str("hello world");
792        assert!(!s.is_inline());
793        assert_eq!(s.as_str(), "hello world");
794    }
795
796    #[test]
797    fn test_from_str_exact_capacity() {
798        let s: SmallString<5> = SmallString::from_str("hello");
799        assert!(s.is_inline());
800        assert_eq!(s.as_str(), "hello");
801    }
802
803    #[test]
804    fn test_from_string_heap_preserved() {
805        let heap = String::from("hello world this is a long string");
806        let s: SmallString<4> = SmallString::from_string(heap.clone());
807        assert!(!s.is_inline());
808        assert_eq!(s.as_str(), heap);
809    }
810
811    #[test]
812    fn test_from_string_small() {
813        let heap = String::from("hi");
814        let s: SmallString<32> = SmallString::from_string(heap);
815        // from_string always stores in Heap variant
816        assert!(!s.is_inline());
817        assert_eq!(s.as_str(), "hi");
818    }
819
820    #[test]
821    fn test_from_utf8_valid() {
822        let s: SmallString<16> = SmallString::from_utf8(b"hello".to_vec()).unwrap();
823        assert!(s.is_inline());
824        assert_eq!(s.as_str(), "hello");
825    }
826
827    #[test]
828    fn test_from_utf8_invalid() {
829        let result: Result<SmallString<16>, _> = SmallString::from_utf8(vec![0xFF, 0xFE]);
830        assert!(result.is_err());
831    }
832
833    #[test]
834    fn test_from_utf8_slice_valid() {
835        let s: SmallString<16> = SmallString::from_utf8_slice(b"hello").unwrap();
836        assert!(s.is_inline());
837        assert_eq!(s.as_str(), "hello");
838    }
839
840    #[test]
841    fn test_from_utf8_slice_invalid() {
842        let result: Result<SmallString<16>, _> = SmallString::from_utf8_slice(&[0xFF, 0xFE]);
843        assert!(result.is_err());
844    }
845
846    #[test]
847    fn test_from_utf8_lossy_valid() {
848        let s: SmallString<16> = SmallString::from_utf8_lossy(b"hello");
849        assert!(s.is_inline());
850        assert_eq!(s.as_str(), "hello");
851    }
852
853    #[test]
854    fn test_from_utf8_lossy_invalid() {
855        let s: SmallString<16> = SmallString::from_utf8_lossy(&[0xFF, 0xFE]);
856        // Replacement character(s)
857        assert_eq!(s.as_str(), "\u{FFFD}\u{FFFD}");
858    }
859
860    #[test]
861    fn test_from_utf8_lossy_spill() {
862        let bytes = b"hello world this is long";
863        let s: SmallString<4> = SmallString::from_utf8_lossy(bytes);
864        assert!(!s.is_inline());
865        assert_eq!(s.as_str(), "hello world this is long");
866    }
867
868    // -----------------------------------------------------------------------
869    // From trait impls
870    // -----------------------------------------------------------------------
871
872    #[test]
873    fn test_from_str_trait_inline() {
874        let s: SmallString<16> = SmallString::from("hi");
875        assert!(s.is_inline());
876    }
877
878    #[test]
879    fn test_from_str_trait_spill() {
880        let s: SmallString<4> = SmallString::from("long string");
881        assert!(!s.is_inline());
882    }
883
884    #[test]
885    fn test_from_string_trait() {
886        let s: SmallString<32> = SmallString::from(String::from("hi"));
887        assert!(!s.is_inline());
888        assert_eq!(s.as_str(), "hi");
889    }
890
891    // -----------------------------------------------------------------------
892    // Inspection methods
893    // -----------------------------------------------------------------------
894
895    #[test]
896    fn test_capacity_inline() {
897        let s: SmallString<64> = SmallString::from_str("hello");
898        assert_eq!(s.capacity(), 64);
899    }
900
901    #[test]
902    fn test_capacity_heap() {
903        let s: SmallString<4> = SmallString::from_str("hello world, this is a test!");
904        assert!(!s.is_inline());
905        assert!(s.capacity() >= s.len());
906    }
907
908    #[test]
909    fn test_len() {
910        let s: SmallString<32> = SmallString::from_str("héllo");
911        assert_eq!(s.len(), 6);
912    }
913
914    // -----------------------------------------------------------------------
915    // Mutation methods
916    // -----------------------------------------------------------------------
917
918    #[test]
919    fn test_clear_inline() {
920        let mut s: SmallString<16> = SmallString::from_str("hello");
921        assert!(s.is_inline());
922        s.clear();
923        assert!(s.is_empty());
924        assert!(s.is_inline());
925        assert_eq!(s.capacity(), 16);
926    }
927
928    #[test]
929    fn test_clear_heap() {
930        let mut s: SmallString<4> = SmallString::from_str("long string content");
931        assert!(!s.is_inline());
932        s.clear();
933        assert!(s.is_empty());
934        assert!(!s.is_inline());
935    }
936
937    #[test]
938    fn test_clear_and_reuse_inline() {
939        let mut s: SmallString<16> = SmallString::from_str("hello");
940        s.clear();
941        s.push_str("world");
942        assert!(s.is_inline());
943        assert_eq!(s.as_str(), "world");
944    }
945
946    #[test]
947    fn test_clear_and_reuse_heap() {
948        let mut s: SmallString<4> = SmallString::from_str("long string content");
949        s.clear();
950        s.push_str("abc");
951        assert!(!s.is_inline());
952        assert_eq!(s.as_str(), "abc");
953    }
954
955    #[test]
956    fn test_push_char_inline() {
957        let mut s: SmallString<16> = SmallString::new();
958        s.push('a');
959        s.push('b');
960        s.push('c');
961        assert!(s.is_inline());
962        assert_eq!(s.as_str(), "abc");
963    }
964
965    #[test]
966    fn test_push_char_spill() {
967        let mut s: SmallString<4> = SmallString::from_str("abc");
968        assert!(s.is_inline());
969        s.push('d');
970        // exactly at capacity, still inline
971        assert!(s.is_inline());
972        assert_eq!(s.as_str(), "abcd");
973
974        s.push('e');
975        assert!(!s.is_inline());
976        assert_eq!(s.as_str(), "abcde");
977    }
978
979    #[test]
980    fn test_push_multibyte_char_stays_inline() {
981        let mut s: SmallString<8> = SmallString::new();
982        s.push('€');
983        assert!(s.is_inline());
984        assert_eq!(s.as_str(), "€");
985    }
986
987    #[test]
988    fn test_push_multibyte_char_spill() {
989        let mut s: SmallString<4> = SmallString::from_str("a");
990        // '🦀' is 4 bytes, 'a' is 1 byte, so this spills
991        s.push('🦀');
992        assert!(!s.is_inline());
993        assert_eq!(s.as_str(), "a🦀");
994    }
995
996    #[test]
997    fn test_push_str_spill_with_char() {
998        let mut s: SmallString<4> = SmallString::from_str("a");
999        s.push_str("bcd");
1000        assert!(s.is_inline());
1001        assert_eq!(s.as_str(), "abcd");
1002
1003        s.push_str("e");
1004        assert!(!s.is_inline());
1005        assert_eq!(s.as_str(), "abcde");
1006    }
1007
1008    #[test]
1009    fn test_push_str_exact_boundary() {
1010        let mut s: SmallString<5> = SmallString::from_str("hello");
1011        assert!(s.is_inline());
1012        // push empty str on full buffer
1013        s.push_str("");
1014        assert!(s.is_inline());
1015        assert_eq!(s.as_str(), "hello");
1016    }
1017
1018    #[test]
1019    fn test_pop_inline() {
1020        let mut s: SmallString<16> = SmallString::from_str("hello");
1021        assert_eq!(s.pop(), Some('o'));
1022        assert_eq!(s.pop(), Some('l'));
1023        assert_eq!(s.as_str(), "hel");
1024    }
1025
1026    #[test]
1027    fn test_pop_heap() {
1028        let mut s: SmallString<4> = SmallString::from_str("hello!!!");
1029        assert!(!s.is_inline());
1030        assert_eq!(s.pop(), Some('!'));
1031        assert_eq!(s.pop(), Some('!'));
1032        assert_eq!(s.as_str(), "hello!");
1033    }
1034
1035    #[test]
1036    fn test_pop_empty() {
1037        let mut s: SmallString<16> = SmallString::new();
1038        assert_eq!(s.pop(), None);
1039    }
1040
1041    #[test]
1042    fn test_pop_empty_after_clear() {
1043        let mut s: SmallString<16> = SmallString::from_str("a");
1044        s.clear();
1045        assert_eq!(s.pop(), None);
1046    }
1047
1048    #[test]
1049    fn test_pop_multibyte() {
1050        let mut s: SmallString<16> = SmallString::from_str("a🦀b");
1051        assert_eq!(s.pop(), Some('b'));
1052        assert_eq!(s.pop(), Some('🦀'));
1053        assert_eq!(s.as_str(), "a");
1054    }
1055
1056    #[test]
1057    fn test_truncate_inline() {
1058        let mut s: SmallString<16> = SmallString::from_str("hello world");
1059        s.truncate(5);
1060        assert_eq!(s.as_str(), "hello");
1061        assert!(s.is_inline());
1062    }
1063
1064    #[test]
1065    fn test_truncate_heap() {
1066        let mut s: SmallString<4> = SmallString::from_str("hello world");
1067        assert!(!s.is_inline());
1068        s.truncate(5);
1069        assert_eq!(s.as_str(), "hello");
1070        assert!(!s.is_inline());
1071    }
1072
1073    #[test]
1074    fn test_truncate_zero() {
1075        let mut s: SmallString<16> = SmallString::from_str("hello");
1076        s.truncate(0);
1077        assert!(s.is_empty());
1078    }
1079
1080    #[test]
1081    fn test_truncate_past_len() {
1082        let mut s: SmallString<16> = SmallString::from_str("hi");
1083        s.truncate(100);
1084        assert_eq!(s.as_str(), "hi");
1085    }
1086
1087    #[test]
1088    fn test_reserve_no_spill() {
1089        let mut s: SmallString<16> = SmallString::from_str("hi");
1090        s.reserve(4);
1091        assert!(s.is_inline());
1092    }
1093
1094    #[test]
1095    fn test_reserve_triggers_spill() {
1096        let mut s: SmallString<8> = SmallString::from_str("hi");
1097        s.reserve(10);
1098        assert!(!s.is_inline());
1099        assert_eq!(s.as_str(), "hi");
1100    }
1101
1102    #[test]
1103    fn test_reserve_on_heap() {
1104        let mut s: SmallString<4> = SmallString::from_str("hello world");
1105        assert!(!s.is_inline());
1106        s.reserve(50);
1107        // `String::reserve` guarantees room for `len + additional` bytes,
1108        // regardless of the pre-existing capacity.
1109        assert!(s.capacity() >= s.len() + 50);
1110        assert_eq!(s.as_str(), "hello world");
1111    }
1112
1113    // -----------------------------------------------------------------------
1114    // Access methods
1115    // -----------------------------------------------------------------------
1116
1117    #[test]
1118    fn test_as_str() {
1119        let s: SmallString<16> = SmallString::from_str("hello");
1120        assert_eq!(s.as_str(), "hello");
1121    }
1122
1123    #[test]
1124    fn test_as_mut_str() {
1125        let mut s: SmallString<16> = SmallString::from_str("hello");
1126        let ms = s.as_mut_str();
1127        ms.make_ascii_uppercase();
1128        assert_eq!(s.as_str(), "HELLO");
1129    }
1130
1131    #[test]
1132    fn test_as_bytes() {
1133        let s: SmallString<16> = SmallString::from_str("hello");
1134        assert_eq!(s.as_bytes(), b"hello");
1135    }
1136
1137    #[test]
1138    fn test_as_bytes_mut() {
1139        let mut s: SmallString<16> = SmallString::from_str("hello");
1140        let bytes = unsafe { s.as_bytes_mut() };
1141        bytes[0] = b'H';
1142        assert_eq!(s.as_str(), "Hello");
1143    }
1144
1145    #[test]
1146    fn test_as_bytes_heap() {
1147        let s: SmallString<4> = SmallString::from_str("hello world");
1148        assert!(!s.is_inline());
1149        assert_eq!(s.as_bytes(), b"hello world");
1150    }
1151
1152    // -----------------------------------------------------------------------
1153    // Deref and DerefMut
1154    // -----------------------------------------------------------------------
1155
1156    #[test]
1157    fn test_deref_inline() {
1158        let s: SmallString<16> = SmallString::from_str("hello");
1159        let r: &str = &s;
1160        assert_eq!(r, "hello");
1161    }
1162
1163    #[test]
1164    fn test_deref_heap() {
1165        let s: SmallString<4> = SmallString::from_str("long string");
1166        let r: &str = &s;
1167        assert_eq!(r, "long string");
1168    }
1169
1170    #[test]
1171    fn test_deref_mut() {
1172        let mut s: SmallString<16> = SmallString::from_str("hello");
1173        let r: &mut str = &mut s;
1174        r.make_ascii_uppercase();
1175        assert_eq!(s.as_str(), "HELLO");
1176    }
1177
1178    // -----------------------------------------------------------------------
1179    // Display, Debug, fmt::Write
1180    // -----------------------------------------------------------------------
1181
1182    #[test]
1183    fn test_display() {
1184        let s: SmallString<16> = SmallString::from_str("hello");
1185        assert_eq!(format!("{}", s), "hello");
1186    }
1187
1188    #[test]
1189    fn test_display_heap() {
1190        let s: SmallString<4> = SmallString::from_str("hello world");
1191        assert_eq!(format!("{}", s), "hello world");
1192    }
1193
1194    #[test]
1195    fn test_debug_inline() {
1196        let s: SmallString<16> = SmallString::from_str("hello");
1197        assert_eq!(format!("{s:?}"), "\"hello\"");
1198    }
1199
1200    #[test]
1201    fn test_debug_heap() {
1202        let s: SmallString<4> = SmallString::from_str("hello world");
1203        assert_eq!(format!("{s:?}"), "\"hello world\"");
1204    }
1205
1206    #[test]
1207    fn test_fmt_write() {
1208        let mut s: SmallString<16> = SmallString::new();
1209        write!(&mut s, "hello world {}", 42).unwrap();
1210        assert_eq!(s.as_str(), "hello world 42");
1211        assert!(s.is_inline());
1212    }
1213
1214    #[test]
1215    fn test_fmt_write_spill() {
1216        let mut s: SmallString<4> = SmallString::new();
1217        write!(&mut s, "hello world").unwrap();
1218        assert!(!s.is_inline());
1219        assert_eq!(s.as_str(), "hello world");
1220    }
1221
1222    // -----------------------------------------------------------------------
1223    // Equality
1224    // -----------------------------------------------------------------------
1225
1226    #[test]
1227    fn test_eq_inline_inline() {
1228        let a: SmallString<16> = SmallString::from_str("hello");
1229        let b: SmallString<16> = SmallString::from_str("hello");
1230        assert_eq!(a, b);
1231    }
1232
1233    #[test]
1234    fn test_eq_inline_heap() {
1235        let a: SmallString<4> = SmallString::from_str("hello");
1236        let b: SmallString<16> = SmallString::from_str("hello");
1237        assert!(!a.is_inline());
1238        assert!(b.is_inline());
1239        assert_eq!(a, b);
1240    }
1241
1242    #[test]
1243    fn test_eq_cross_n() {
1244        let a: SmallString<8> = SmallString::from_str("test");
1245        let b: SmallString<32> = SmallString::from_str("test");
1246        assert_eq!(a, b);
1247    }
1248
1249    #[test]
1250    fn test_eq_inequality() {
1251        let a: SmallString<16> = SmallString::from_str("abc");
1252        let b: SmallString<16> = SmallString::from_str("xyz");
1253        assert_ne!(a, b);
1254    }
1255
1256    #[test]
1257    fn test_partial_eq_str() {
1258        let s: SmallString<16> = SmallString::from_str("hello");
1259        assert_eq!(s, *"hello");
1260    }
1261
1262    #[test]
1263    fn test_partial_eq_ref_str() {
1264        let s: SmallString<16> = SmallString::from_str("hello");
1265        let r: &str = "hello";
1266        assert_eq!(s, r);
1267    }
1268
1269    #[test]
1270    fn test_partial_eq_str_ref_left() {
1271        let s: SmallString<16> = SmallString::from_str("hello");
1272        assert_eq!("hello", s);
1273    }
1274
1275    #[test]
1276    fn test_partial_eq_string() {
1277        let s: SmallString<16> = SmallString::from_str("hello");
1278        let heap = String::from("hello");
1279        assert_eq!(s, heap);
1280    }
1281
1282    // -----------------------------------------------------------------------
1283    // Ordering
1284    // -----------------------------------------------------------------------
1285
1286    #[test]
1287    fn test_ord() {
1288        let a: SmallString<16> = SmallString::from_str("abc");
1289        let b: SmallString<16> = SmallString::from_str("xyz");
1290        assert!(a < b);
1291        assert!(b > a);
1292    }
1293
1294    #[test]
1295    fn test_ord_cross_n() {
1296        let a: SmallString<4> = SmallString::from_str("abc");
1297        let b: SmallString<32> = SmallString::from_str("xyz");
1298        assert!(a < b);
1299    }
1300
1301    #[test]
1302    fn test_ord_equal() {
1303        let a: SmallString<16> = SmallString::from_str("same");
1304        let b: SmallString<16> = SmallString::from_str("same");
1305        assert_eq!(a.cmp(&b), core::cmp::Ordering::Equal);
1306    }
1307
1308    #[test]
1309    fn test_partial_ord_cross_m() {
1310        let a: SmallString<8> = SmallString::from_str("a");
1311        let b: SmallString<32> = SmallString::from_str("b");
1312        assert!(a < b);
1313    }
1314
1315    // -----------------------------------------------------------------------
1316    // Hash
1317    // -----------------------------------------------------------------------
1318
1319    #[test]
1320    fn test_hash_inline() {
1321        use std::{
1322            collections::hash_map::DefaultHasher,
1323            hash::{Hash, Hasher},
1324        };
1325
1326        let s: SmallString<16> = SmallString::from_str("hello");
1327        let mut hasher = DefaultHasher::new();
1328        s.hash(&mut hasher);
1329        let h1 = hasher.finish();
1330
1331        let mut hasher = DefaultHasher::new();
1332        "hello".hash(&mut hasher);
1333        let h2 = hasher.finish();
1334        assert_eq!(h1, h2);
1335    }
1336
1337    #[test]
1338    fn test_hash_inline_and_heap_same() {
1339        use std::{
1340            collections::hash_map::DefaultHasher,
1341            hash::{Hash, Hasher},
1342        };
1343
1344        let inline: SmallString<32> = SmallString::from_str("hello world");
1345        let heap: SmallString<4> = SmallString::from_str("hello world");
1346
1347        let mut h1 = DefaultHasher::new();
1348        inline.hash(&mut h1);
1349        let mut h2 = DefaultHasher::new();
1350        heap.hash(&mut h2);
1351        assert_eq!(h1.finish(), h2.finish());
1352    }
1353
1354    #[test]
1355    fn test_hash_in_hashmap() {
1356        use std::collections::HashSet;
1357
1358        let a: SmallString<16> = SmallString::from_str("key1");
1359        let b: SmallString<16> = SmallString::from_str("key1");
1360
1361        let mut set = HashSet::new();
1362        set.insert(a);
1363        assert!(set.contains(&b));
1364    }
1365
1366    // -----------------------------------------------------------------------
1367    // Borrow / BorrowMut / AsRef
1368    // -----------------------------------------------------------------------
1369
1370    #[test]
1371    fn test_borrow_str() {
1372        use std::borrow::Borrow;
1373        let s: SmallString<16> = SmallString::from_str("hello");
1374        let r: &str = s.borrow();
1375        assert_eq!(r, "hello");
1376    }
1377
1378    #[test]
1379    fn test_borrow_mut_str() {
1380        use std::borrow::BorrowMut;
1381        let mut s: SmallString<16> = SmallString::from_str("hello");
1382        let r: &mut str = s.borrow_mut();
1383        r.make_ascii_uppercase();
1384        assert_eq!(s.as_str(), "HELLO");
1385    }
1386
1387    #[test]
1388    fn test_as_ref_str() {
1389        let s: SmallString<16> = SmallString::from_str("hello");
1390        let r: &str = s.as_ref();
1391        assert_eq!(r, "hello");
1392    }
1393
1394    #[test]
1395    fn test_as_ref_bytes() {
1396        let s: SmallString<16> = SmallString::from_str("hello");
1397        let r: &[u8] = s.as_ref();
1398        assert_eq!(r, b"hello");
1399    }
1400
1401    // -----------------------------------------------------------------------
1402    // Clone
1403    // -----------------------------------------------------------------------
1404
1405    #[test]
1406    fn test_clone_inline() {
1407        let s: SmallString<16> = SmallString::from_str("hello");
1408        let c = s.clone();
1409        assert!(c.is_inline());
1410        assert_eq!(c, s);
1411    }
1412
1413    #[test]
1414    fn test_clone_heap() {
1415        let s: SmallString<4> = SmallString::from_str("hello world");
1416        assert!(!s.is_inline());
1417        let c = s.clone();
1418        assert!(!c.is_inline());
1419        assert_eq!(c, s);
1420    }
1421
1422    #[test]
1423    fn test_clone_independent() {
1424        let s: SmallString<16> = SmallString::from_str("hello");
1425        let mut c = s.clone();
1426        c.push_str(" world");
1427        assert_eq!(s.as_str(), "hello");
1428        assert_eq!(c.as_str(), "hello world");
1429    }
1430
1431    // -----------------------------------------------------------------------
1432    // FromIterator
1433    // -----------------------------------------------------------------------
1434
1435    #[test]
1436    fn test_from_iter_chars_empty() {
1437        let s: SmallString<16> = SmallString::from_iter("".chars());
1438        assert!(s.is_empty());
1439    }
1440
1441    #[test]
1442    fn test_from_iter_chars_inline() {
1443        let s: SmallString<16> = SmallString::from_iter("hello".chars());
1444        assert!(s.is_inline());
1445        assert_eq!(s.as_str(), "hello");
1446    }
1447
1448    #[test]
1449    fn test_from_iter_chars_spill() {
1450        let s: SmallString<4> = SmallString::from_iter("hello world".chars());
1451        assert!(!s.is_inline());
1452        assert_eq!(s.as_str(), "hello world");
1453    }
1454
1455    #[test]
1456    fn test_from_iter_strs_empty() {
1457        let s: SmallString<16> = [""].iter().copied().collect::<SmallString<16>>();
1458        assert!(s.is_empty());
1459    }
1460
1461    #[test]
1462    fn test_from_iter_strs_inline() {
1463        let parts = ["hello", " ", "world"];
1464        let s: SmallString<32> = parts.iter().copied().collect();
1465        assert!(s.is_inline());
1466        assert_eq!(s.as_str(), "hello world");
1467    }
1468
1469    #[test]
1470    fn test_from_iter_strs_spill() {
1471        let parts = ["hello", " ", "world", " ", "this is long"];
1472        let s: SmallString<4> = parts.iter().copied().collect();
1473        assert!(!s.is_inline());
1474        assert_eq!(s.as_str(), "hello world this is long");
1475    }
1476
1477    // -----------------------------------------------------------------------
1478    // Extend
1479    // -----------------------------------------------------------------------
1480
1481    #[test]
1482    fn test_extend_chars_inline() {
1483        let mut s: SmallString<16> = SmallString::from_str("he");
1484        s.extend("llo".chars());
1485        assert!(s.is_inline());
1486        assert_eq!(s.as_str(), "hello");
1487    }
1488
1489    #[test]
1490    fn test_extend_chars_spill() {
1491        let mut s: SmallString<4> = SmallString::from_str("a");
1492        s.extend("bcdef".chars());
1493        assert!(!s.is_inline());
1494        assert_eq!(s.as_str(), "abcdef");
1495    }
1496
1497    #[test]
1498    fn test_extend_strs_inline() {
1499        let mut s: SmallString<16> = SmallString::from_str("hello");
1500        s.extend([" ", "world"]);
1501        assert!(s.is_inline());
1502        assert_eq!(s.as_str(), "hello world");
1503    }
1504
1505    #[test]
1506    fn test_extend_strs_spill() {
1507        let mut s: SmallString<4> = SmallString::from_str("a");
1508        s.extend(["bc", "def"]);
1509        assert!(!s.is_inline());
1510        assert_eq!(s.as_str(), "abcdef");
1511    }
1512
1513    // -----------------------------------------------------------------------
1514    // Edge cases
1515    // -----------------------------------------------------------------------
1516
1517    #[test]
1518    fn test_zero_capacity() {
1519        let mut s: SmallString<0> = SmallString::new();
1520        assert!(s.is_inline());
1521        assert!(s.is_empty());
1522        assert_eq!(s.capacity(), 0);
1523
1524        // Any push should spill immediately
1525        s.push_str("x");
1526        assert!(!s.is_inline());
1527        assert_eq!(s.as_str(), "x");
1528
1529        // Push char on zero capacity
1530        let mut s2: SmallString<0> = SmallString::new();
1531        s2.push('a');
1532        assert!(!s2.is_inline());
1533        assert_eq!(s2.as_str(), "a");
1534    }
1535
1536    #[test]
1537    fn test_zero_capacity_from_str() {
1538        let s: SmallString<0> = SmallString::from_str("");
1539        assert!(s.is_inline());
1540        assert!(s.is_empty());
1541
1542        let s2: SmallString<0> = SmallString::from_str("x");
1543        assert!(!s2.is_inline());
1544        assert_eq!(s2.as_str(), "x");
1545    }
1546
1547    #[test]
1548    fn test_multibyte_char_boundary() {
1549        let mut s: SmallString<8> = SmallString::new();
1550        s.push_str("a🦀b"); // 'a'=1, '🦀'=4, 'b'=1 => total 6
1551        assert!(s.is_inline());
1552        assert_eq!(s.len(), 6);
1553        assert_eq!(s.as_str(), "a🦀b");
1554
1555        // Ensure indexing / slicing works correctly
1556        let chars: Vec<char> = s.chars().collect();
1557        assert_eq!(chars, vec!['a', '🦀', 'b']);
1558    }
1559
1560    #[test]
1561    fn test_deref_methods_available() {
1562        let s: SmallString<16> = SmallString::from_str("hello world");
1563        // Methods from str through Deref
1564        assert!(s.contains("world"));
1565        assert!(s.starts_with("hello"));
1566        assert_eq!(s.find('w'), Some(6));
1567        let words: Vec<&str> = s.split(' ').collect();
1568        assert_eq!(words, vec!["hello", "world"]);
1569    }
1570
1571    #[test]
1572    fn test_roundtrip_format() {
1573        let s: SmallString<16> = SmallString::from_str("test");
1574        let formatted = format!("{}", s);
1575        let back: SmallString<16> = SmallString::from_str(&formatted);
1576        assert_eq!(s, back);
1577    }
1578
1579    #[test]
1580    fn test_empty_str_operations() {
1581        let mut s: SmallString<16> = SmallString::from_str("");
1582        assert!(s.is_empty());
1583        s.push_str("");
1584        assert!(s.is_empty());
1585        s.push_str("a");
1586        assert_eq!(s.len(), 1);
1587    }
1588
1589    #[test]
1590    fn test_from_small_string_inline_to_inline() {
1591        let source: SmallString<16> = SmallString::from_str("hello");
1592        assert!(source.is_inline());
1593
1594        let dest: SmallString<32> = SmallString::from_small_string(source);
1595        assert!(dest.is_inline());
1596        assert_eq!(dest.as_str(), "hello");
1597    }
1598
1599    #[test]
1600    fn test_from_small_string_inline_to_spill() {
1601        let source: SmallString<16> = SmallString::from_str("hello world");
1602        assert!(source.is_inline());
1603
1604        let dest: SmallString<4> = SmallString::from_small_string(source);
1605        assert!(!dest.is_inline());
1606        assert_eq!(dest.as_str(), "hello world");
1607    }
1608
1609    #[test]
1610    fn test_from_small_string_heap_to_inline() {
1611        // Force heap by exceeding inline capacity
1612        let source: SmallString<4> = SmallString::from_str("hello world this is long");
1613        assert!(!source.is_inline());
1614
1615        // Target has enough capacity to fit inline
1616        let dest: SmallString<64> = SmallString::from_small_string(source);
1617        assert!(dest.is_inline());
1618        assert_eq!(dest.as_str(), "hello world this is long");
1619    }
1620
1621    #[test]
1622    fn test_from_small_string_heap_to_heap() {
1623        // Force heap by exceeding inline capacity
1624        let source: SmallString<16> = SmallString::from_str("hello world this is even longer string content");
1625        assert!(!source.is_inline());
1626
1627        // Target too small to fit inline
1628        let dest: SmallString<4> = SmallString::from_small_string(source);
1629        assert!(!dest.is_inline());
1630        assert_eq!(dest.as_str(), "hello world this is even longer string content");
1631    }
1632
1633    #[test]
1634    fn test_from_small_string_empty() {
1635        let source: SmallString<16> = SmallString::new();
1636        assert!(source.is_inline());
1637        assert!(source.is_empty());
1638
1639        let dest: SmallString<8> = SmallString::from_small_string(source);
1640        assert!(dest.is_inline());
1641        assert!(dest.is_empty());
1642    }
1643
1644    #[test]
1645    fn test_from_small_string_exact_capacity() {
1646        let source: SmallString<16> = SmallString::from_str("abcd");
1647        assert!(source.is_inline());
1648
1649        let dest: SmallString<4> = SmallString::from_small_string(source);
1650        assert!(dest.is_inline());
1651        assert_eq!(dest.as_str(), "abcd");
1652    }
1653
1654    #[cfg(feature = "serde")]
1655    #[test]
1656    fn test_serde_serializes_as_flat_string() {
1657        let inline: SmallString<16> = SmallString::from_str("hello");
1658        let heap: SmallString<2> = SmallString::from_str("hello");
1659        assert!(inline.is_inline());
1660        assert!(!heap.is_inline());
1661
1662        // Both representations produce the same, plain JSON string.
1663        assert_eq!(serde_json::to_string(&inline).unwrap(), "\"hello\"");
1664        assert_eq!(serde_json::to_string(&heap).unwrap(), "\"hello\"");
1665    }
1666
1667    #[cfg(feature = "serde")]
1668    #[test]
1669    fn test_serde_deserializes_into_inline_or_heap() {
1670        let inline: SmallString<16> = serde_json::from_str("\"hello\"").unwrap();
1671        assert!(inline.is_inline());
1672        assert_eq!(inline.as_str(), "hello");
1673
1674        // The value doesn't fit inline, so it must land on the heap even
1675        // though the source JSON is identical.
1676        let heap: SmallString<2> = serde_json::from_str("\"hello\"").unwrap();
1677        assert!(!heap.is_inline());
1678        assert_eq!(heap.as_str(), "hello");
1679    }
1680
1681    #[cfg(feature = "serde")]
1682    #[test]
1683    fn test_serde_roundtrip_both_variants() {
1684        let inline: SmallString<16> = SmallString::from_str("hello");
1685        let json = serde_json::to_string(&inline).unwrap();
1686        let back: SmallString<16> = serde_json::from_str(&json).unwrap();
1687        assert_eq!(back, inline);
1688        assert!(back.is_inline());
1689
1690        let heap: SmallString<4> = SmallString::from_str("hello world");
1691        let json = serde_json::to_string(&heap).unwrap();
1692        let back: SmallString<4> = serde_json::from_str(&json).unwrap();
1693        assert_eq!(back, heap);
1694        assert!(!back.is_inline());
1695    }
1696
1697    #[cfg(feature = "serde")]
1698    #[test]
1699    fn test_serde_rejects_non_string() {
1700        let result: Result<SmallString<16>, _> = serde_json::from_str("42");
1701        assert!(result.is_err());
1702    }
1703
1704    // -----------------------------------------------------------------------
1705    // Inline placement of byte constructors
1706    // -----------------------------------------------------------------------
1707
1708    #[test]
1709    fn test_from_utf8_inline_when_fits() {
1710        let s: SmallString<16> = SmallString::from_utf8(b"hello".to_vec()).unwrap();
1711        assert!(s.is_inline());
1712        assert_eq!(s.as_str(), "hello");
1713    }
1714
1715    #[test]
1716    fn test_from_utf8_spills_when_too_long() {
1717        let s: SmallString<2> = SmallString::from_utf8(b"hello".to_vec()).unwrap();
1718        assert!(!s.is_inline());
1719        assert_eq!(s.as_str(), "hello");
1720    }
1721
1722    #[test]
1723    fn test_from_utf8_empty_is_inline() {
1724        let s: SmallString<4> = SmallString::from_utf8(Vec::new()).unwrap();
1725        assert!(s.is_inline());
1726        assert!(s.is_empty());
1727    }
1728
1729    #[test]
1730    fn test_from_utf8_lossy_invalid_fits_inline() {
1731        // Invalid input is still stored inline when the replaced result fits.
1732        let s: SmallString<16> = SmallString::from_utf8_lossy(&[0xFF, 0xFE]);
1733        assert!(s.is_inline());
1734        assert_eq!(s.as_str(), "\u{FFFD}\u{FFFD}");
1735    }
1736
1737    // -----------------------------------------------------------------------
1738    // Construction / conversion additions
1739    // -----------------------------------------------------------------------
1740
1741    #[test]
1742    fn test_inline_size() {
1743        assert_eq!(<SmallString<8>>::inline_size(), 8);
1744        assert_eq!(<SmallString<0>>::inline_size(), 0);
1745    }
1746
1747    #[test]
1748    fn test_from_string_empty_has_no_allocation() {
1749        let s: SmallString<8> = SmallString::from_string(String::new());
1750        assert!(s.is_inline());
1751        assert!(s.is_empty());
1752    }
1753
1754    #[test]
1755    fn test_from_string_empty_with_capacity_stays_heap() {
1756        let source = String::with_capacity(8);
1757        let s: SmallString<64> = SmallString::from_string(source);
1758        assert!(!s.is_inline());
1759        assert!(s.is_empty());
1760    }
1761
1762    #[test]
1763    fn test_into_string() {
1764        let inline: SmallString<16> = SmallString::from_str("hi");
1765        assert_eq!(inline.into_string(), "hi");
1766
1767        let heap: SmallString<2> = SmallString::from_str("hello");
1768        assert!(!heap.is_inline());
1769        assert_eq!(heap.into_string(), "hello");
1770    }
1771
1772    #[test]
1773    fn test_into_bytes() {
1774        let s: SmallString<16> = SmallString::from_str("hé");
1775        assert_eq!(s.into_bytes(), b"h\xC3\xA9".to_vec());
1776    }
1777
1778    #[test]
1779    fn test_from_small_string_for_string() {
1780        let s: SmallString<4> = SmallString::from_str("hello");
1781        let owned: String = String::from(s);
1782        assert_eq!(owned, "hello");
1783    }
1784
1785    #[test]
1786    fn test_from_str_trait_and_parse() {
1787        use core::str::FromStr;
1788
1789        let s = <SmallString<8> as FromStr>::from_str("hi").unwrap();
1790        assert!(s.is_inline());
1791        assert_eq!(s.as_str(), "hi");
1792
1793        let parsed: SmallString<16> = "parsed".parse().unwrap();
1794        assert_eq!(parsed.as_str(), "parsed");
1795    }
1796
1797    #[test]
1798    fn test_as_mut_str_impl() {
1799        let mut s: SmallString<16> = SmallString::from_str("hello");
1800        let r: &mut str = s.as_mut();
1801        r.make_ascii_uppercase();
1802        assert_eq!(s.as_str(), "HELLO");
1803    }
1804
1805    // -----------------------------------------------------------------------
1806    // Shrinking
1807    // -----------------------------------------------------------------------
1808
1809    #[test]
1810    fn test_shrink_to_fit_moves_back_inline() {
1811        let mut s: SmallString<8> = SmallString::from_str("hello world");
1812        assert!(!s.is_inline());
1813        s.truncate(5);
1814        assert!(!s.is_inline());
1815        s.shrink_to_fit();
1816        assert!(s.is_inline());
1817        assert_eq!(s.as_str(), "hello");
1818    }
1819
1820    #[test]
1821    fn test_shrink_to_fit_stays_heap_when_too_long() {
1822        let mut s: SmallString<2> = SmallString::from_str("hello");
1823        s.shrink_to_fit();
1824        assert!(!s.is_inline());
1825        assert_eq!(s.as_str(), "hello");
1826    }
1827
1828    #[test]
1829    fn test_shrink_to_moves_back_inline() {
1830        let mut s: SmallString<8> = SmallString::from_str("hello world");
1831        assert!(!s.is_inline());
1832        s.truncate(5);
1833        s.shrink_to(1);
1834        assert!(s.is_inline());
1835        assert_eq!(s.as_str(), "hello");
1836    }
1837
1838    #[test]
1839    fn test_shrink_to_on_inline_is_noop() {
1840        let mut s: SmallString<16> = SmallString::from_str("hello");
1841        s.shrink_to(100);
1842        assert!(s.is_inline());
1843        s.shrink_to_fit();
1844        assert!(s.is_inline());
1845        assert_eq!(s.as_str(), "hello");
1846    }
1847
1848    #[test]
1849    fn test_reserve_exact_spills() {
1850        let mut s: SmallString<4> = SmallString::from_str("ab");
1851        s.reserve_exact(10);
1852        assert!(!s.is_inline());
1853        assert!(s.capacity() >= 12);
1854        assert_eq!(s.as_str(), "ab");
1855    }
1856
1857    // -----------------------------------------------------------------------
1858    // Reverse equality and hashing helpers
1859    // -----------------------------------------------------------------------
1860
1861    #[test]
1862    fn test_reverse_partial_eq_string_and_str() {
1863        let s: SmallString<16> = SmallString::from_str("hello");
1864        assert_eq!(String::from("hello"), s);
1865        assert!(*"hello" == s);
1866        assert!(*"nope" != s);
1867    }
1868
1869    #[test]
1870    fn test_borrow_str_hashmap_lookup() {
1871        use std::collections::HashMap;
1872
1873        let mut map = HashMap::new();
1874        let key: SmallString<16> = SmallString::from_str("key1");
1875        map.insert(key, 1);
1876        assert_eq!(map.get("key1"), Some(&1));
1877    }
1878
1879    // -----------------------------------------------------------------------
1880    // More edge cases
1881    // -----------------------------------------------------------------------
1882
1883    #[test]
1884    #[should_panic]
1885    fn test_truncate_non_char_boundary_panics() {
1886        let mut s: SmallString<16> = SmallString::from_str("a🦀b");
1887        // 2 is inside the 4-byte crab.
1888        s.truncate(2);
1889    }
1890
1891    #[test]
1892    fn test_truncate_past_len_is_noop_even_off_boundary() {
1893        let mut s: SmallString<16> = SmallString::from_str("a🦀b");
1894        s.truncate(100);
1895        assert_eq!(s.as_str(), "a🦀b");
1896    }
1897
1898    #[test]
1899    fn test_push_multibyte_exact_boundary() {
1900        // 'a' (1 byte) + '🦀' (4 bytes) == 5 == N, so it stays inline.
1901        let mut s: SmallString<5> = SmallString::from_str("a");
1902        s.push('🦀');
1903        assert!(s.is_inline());
1904        assert_eq!(s.as_str(), "a🦀");
1905    }
1906
1907    #[test]
1908    fn test_fmt_write_char() {
1909        use core::fmt::Write;
1910
1911        let mut s: SmallString<16> = SmallString::new();
1912        s.write_char('é').unwrap();
1913        assert_eq!(s.as_str(), "é");
1914    }
1915
1916    #[test]
1917    fn test_pop_keeps_heap_storage() {
1918        let mut s: SmallString<2> = SmallString::from_str("hello");
1919        assert!(!s.is_inline());
1920        assert_eq!(s.pop(), Some('o'));
1921        assert!(!s.is_inline());
1922        assert_eq!(s.as_str(), "hell");
1923    }
1924}