Skip to main content

small_collections/
string.rs

1//! UTF-8 string that lives on the stack and spills to the heap.
2//!
3//! Provides [`SmallString`] — backed by `heapless::String<N>` (stack) and
4//! `std::string::String` (heap).  Spill uses `from_utf8_unchecked` to skip a redundant
5//! UTF-8 scan since both sources already guarantee valid UTF-8, giving a memcpy-only
6//! migration cost.
7//!
8//! Implements `Deref<Target = str>` so all `&str` methods are available directly.
9//! [`AnyString`] provides an object-safe trait over both backends.
10
11use core::mem::ManuallyDrop;
12use std::borrow::{Borrow, BorrowMut};
13use std::cmp::Ordering;
14use std::fmt;
15use std::hash::{Hash, Hasher};
16use std::ops::{Deref, DerefMut};
17
18/// A trait for abstraction over different string types (Stack, Heap, Small).
19pub trait AnyString {
20    /// Returns a string slice representing the contents.
21    fn as_str(&self) -> &str;
22    /// Returns the number of elements.
23    fn len(&self) -> usize;
24    /// Returns `true` if the collection is empty.
25    fn is_empty(&self) -> bool {
26        self.len() == 0
27    }
28    /// Pushes an item into the collection.
29    fn push_str(&mut self, s: &str);
30    /// Pushes an item into the collection.
31    fn push(&mut self, ch: char);
32    /// Clears all elements from the collection.
33    fn clear(&mut self);
34    /// Removes and returns an item from the collection.
35    fn pop(&mut self) -> Option<char>;
36    /// Truncates the collection to a specific length.
37    fn truncate(&mut self, new_len: usize);
38}
39
40impl AnyString for String {
41    fn as_str(&self) -> &str {
42        self.as_str()
43    }
44    fn len(&self) -> usize {
45        self.len()
46    }
47    fn push_str(&mut self, s: &str) {
48        self.push_str(s);
49    }
50    fn push(&mut self, ch: char) {
51        self.push(ch);
52    }
53    fn clear(&mut self) {
54        self.clear();
55    }
56    fn pop(&mut self) -> Option<char> {
57        self.pop()
58    }
59    fn truncate(&mut self, new_len: usize) {
60        self.truncate(new_len);
61    }
62}
63
64/// A string that lives on the stack for `N` bytes, then spills to the heap.
65///
66/// # Overview
67/// This collection uses a `heapless::String` for stack storage and a
68/// `std::string::String` for heap storage.
69///
70/// # Safety
71/// * `on_stack` tag determines which side of the `StringData` union is active.
72/// * `SmallString` ensures all data remains valid UTF-8 by leveraging the invariants
73///   of the underlying collections and performing manual UTF-8 checks only when necessary.
74pub struct SmallString<const N: usize> {
75    on_stack: bool,
76    data: StringData<N>,
77}
78
79impl<const N: usize> AnyString for SmallString<N> {
80    fn as_str(&self) -> &str {
81        self.as_str()
82    }
83    fn len(&self) -> usize {
84        self.len()
85    }
86    fn push_str(&mut self, s: &str) {
87        self.push_str(s);
88    }
89    fn push(&mut self, ch: char) {
90        self.push(ch);
91    }
92    fn clear(&mut self) {
93        self.clear();
94    }
95    fn pop(&mut self) -> Option<char> {
96        self.pop()
97    }
98    fn truncate(&mut self, new_len: usize) {
99        self.truncate(new_len);
100    }
101}
102
103/// The internal storage for `SmallString`.
104///
105/// We use `ManuallyDrop` because the compiler cannot know which field is active
106/// and therefore cannot automatically drop the correct one.
107union StringData<const N: usize> {
108    stack: ManuallyDrop<heapless::String<N>>,
109    heap: ManuallyDrop<std::string::String>,
110}
111
112impl<const N: usize> SmallString<N> {
113    /// The maximum allowed stack size in bytes (16 KB).
114    pub const MAX_STACK_SIZE: usize = 16 * 1024;
115
116    /// Creates a new empty SmallString.
117    ///
118    /// # Compile-Time Safety
119    /// **Size Limit:** Enforces a limit of 16 KB. Exceeding this fails the build.
120    ///
121    /// ## Test: Valid (Compiles)
122    /// ```rust
123    /// use small_collections::SmallString;
124    /// let s: SmallString<64> = SmallString::new();
125    /// ```
126    ///
127    /// ## Test: Invalid Size (Fails Compilation)
128    /// ```rust,compile_fail
129    /// use small_collections::SmallString;
130    /// // 32 KB string -> Too big for stack guard
131    /// let s: SmallString<32768> = SmallString::new();
132    /// ```
133    ///
134    pub fn new() -> Self {
135        // COMPILER GUARD
136        const {
137            assert!(
138                std::mem::size_of::<Self>() <= Self::MAX_STACK_SIZE,
139                "SmallString is too large! The struct size exceeds the 16KB limit. Reduce N."
140            );
141        }
142
143        Self {
144            on_stack: true,
145            data: StringData {
146                stack: ManuallyDrop::new(heapless::String::new()),
147            },
148        }
149    }
150
151    /// Creates a SmallString from a literal or slice
152    pub fn from_str(s: &str) -> Self {
153        let mut str = Self::new();
154        str.push_str(s);
155        str
156    }
157
158    /// Returns `true` if the string is currently storing data on the stack.
159    #[inline(always)]
160    pub fn is_on_stack(&self) -> bool {
161        self.on_stack
162    }
163
164    // --- Core Operations ---
165
166    /// Appends the given `char` to the end of this `SmallString`.
167    ///
168    /// If the stack capacity `N` is exceeded, this triggers a transparent spill to the heap.
169    #[inline(always)]
170    pub fn push(&mut self, ch: char) {
171        unsafe {
172            if self.on_stack {
173                // 1. Get reference to the inner stack string
174                let stack_str = &mut *self.data.stack;
175
176                // 2. Pre-emptive Size Check
177                // We check if adding the char's byte length exceeds capacity N.
178                if stack_str.len() + ch.len_utf8() > N {
179                    self.spill_to_heap_and_push_char(ch);
180                } else {
181                    // 3. Guaranteed Success
182                    // Since we checked capacity, this push will never fail.
183                    // We can safely ignore the Result.
184                    match stack_str.push(ch) {
185                        Ok(()) => return, // Success: exit early
186                        Err(_) => unreachable!("Stack capacity check failed in push"),
187                    }
188                }
189            } else {
190                (*self.data.heap).push(ch);
191            }
192        }
193    }
194
195    /// Appends a given string slice onto the end of this `SmallString`.
196    ///
197    /// If the stack capacity `N` is exceeded, this triggers a transparent spill to the heap.
198    #[inline(always)]
199    pub fn push_str(&mut self, s: &str) {
200        unsafe {
201            if self.on_stack {
202                let stack_str = &mut *self.data.stack;
203
204                // 2. Pre-emptive Size Check
205                // s.len() returns the byte length, which matches N's definition.
206                if stack_str.len() + s.len() > N {
207                    self.spill_to_heap_and_push_str(s);
208                } else {
209                    // 3. Guaranteed Success
210                    match stack_str.push_str(s) {
211                        Ok(()) => return, // Success: exit early
212                        Err(_) => unreachable!("Stack capacity check failed in push str"),
213                    }
214                }
215            } else {
216                (*self.data.heap).push_str(s);
217            }
218        }
219    }
220
221    /// Returns the length of this `SmallString`, in bytes, not [`char`]s or graphemes.
222    #[inline(always)]
223    pub fn len(&self) -> usize {
224        // We can use the Deref trait here for simplicity,
225        // but explicit access is slightly faster in debug builds.
226        unsafe {
227            if self.on_stack {
228                self.data.stack.len()
229            } else {
230                self.data.heap.len()
231            }
232        }
233    }
234
235    /// Returns `true` if this `SmallString` has a length of zero, and `false` otherwise.
236    #[inline(always)]
237    pub fn is_empty(&self) -> bool {
238        self.len() == 0
239    }
240
241    /// Truncates this `SmallString`, removing all contents.
242    ///
243    /// While this means the `SmallString` will have a length of zero, it does not
244    /// touch its capacity.
245    #[inline(always)]
246    pub fn clear(&mut self) {
247        unsafe {
248            if self.on_stack {
249                (*self.data.stack).clear();
250            } else {
251                (*self.data.heap).clear();
252            }
253        }
254    }
255
256    // --- Spill Logic ---
257
258    #[inline(never)]
259    unsafe fn spill_to_heap_and_push_str(&mut self, pending_str: &str) {
260        unsafe {
261            // 1. Get raw bytes from stack (Instant access)
262            let stack_bytes = self.data.stack.as_bytes();
263            let pending_bytes = pending_str.as_bytes();
264
265            // 2. Calculate exact capacity needed
266            // We use N*2 strategy to prevent frequent re-allocations after spill
267            let total_len = stack_bytes.len() + pending_bytes.len();
268            let cap = std::cmp::max(total_len, N * 2);
269
270            // 3. Allocate RAW MEMORY (Vec<u8>)
271            // We do NOT allocate a String yet. This avoids String metadata overhead.
272            let mut heap_vec = Vec::with_capacity(cap);
273
274            // 4. Bitwise Copy (Memcpy)
275            // extend_from_slice compiles down to a highly optimized 'rep movsb' or SIMD copy.
276            heap_vec.extend_from_slice(stack_bytes);
277            heap_vec.extend_from_slice(pending_bytes);
278
279            // 5. ZERO-COST TRANSFORMATION
280            // We skip the O(N) UTF-8 validation scan.
281            // Safety: We know 'stack_bytes' came from a valid str, and 'pending_str' is a valid str.
282            let new_heap = String::from_utf8_unchecked(heap_vec);
283
284            // 6. State Switch
285            ManuallyDrop::drop(&mut self.data.stack);
286            self.data.heap = ManuallyDrop::new(new_heap);
287            self.on_stack = false;
288        }
289    }
290
291    #[inline(never)]
292    unsafe fn spill_to_heap_and_push_char(&mut self, pending_char: char) {
293        unsafe {
294            let stack_bytes = self.data.stack.as_bytes();
295
296            // char can be up to 4 bytes
297            let cap = std::cmp::max(stack_bytes.len() + 4, N * 2);
298            let mut heap_vec = Vec::with_capacity(cap);
299
300            heap_vec.extend_from_slice(stack_bytes);
301
302            // Encode char directly into the buffer without intermediate String allocation
303            // 'encode_utf8' writes bytes directly to the end of the Vec
304            let char_len = pending_char.len_utf8();
305            let old_len = heap_vec.len();
306
307            // Reserve space and write directly (unsafe speedup)
308            heap_vec.set_len(old_len + char_len);
309            pending_char.encode_utf8(&mut heap_vec[old_len..]);
310
311            let new_heap = String::from_utf8_unchecked(heap_vec);
312
313            ManuallyDrop::drop(&mut self.data.stack);
314            self.data.heap = ManuallyDrop::new(new_heap);
315            self.on_stack = false;
316        }
317    }
318}
319
320// --- Critical Trait: Deref ---
321// This allows SmallString to be used exactly like &str
322// You can call .trim(), .split(), .contains() on it automatically.
323
324impl<const N: usize> Deref for SmallString<N> {
325    type Target = str;
326
327    fn deref(&self) -> &Self::Target {
328        unsafe {
329            if self.on_stack {
330                self.data.stack.as_str()
331            } else {
332                self.data.heap.as_str()
333            }
334        }
335    }
336}
337
338impl<const N: usize> DerefMut for SmallString<N> {
339    fn deref_mut(&mut self) -> &mut Self::Target {
340        unsafe {
341            if self.on_stack {
342                (*self.data.stack).as_mut_str()
343            } else {
344                (*self.data.heap).as_mut_str()
345            }
346        }
347    }
348}
349
350impl<const N: usize> SmallString<N> {
351    /// Extracts a string slice containing the entire `SmallString`.
352    ///
353    /// Equivalent to `&s[..]`.
354    #[inline(always)]
355    pub fn as_str(&self) -> &str {
356        // We can leverage the Deref trait we already wrote
357        &**self
358    }
359
360    /// Extracts a mutable string slice containing the entire `SmallString`.
361    ///
362    /// Equivalent to `&mut s[..]`.
363    #[inline(always)]
364    pub fn as_mut_str(&mut self) -> &mut str {
365        // Leverage DerefMut
366        &mut **self
367    }
368
369    /// Converts a `SmallString` into a byte slice.
370    #[inline(always)]
371    pub fn as_bytes(&self) -> &[u8] {
372        self.as_str().as_bytes()
373    }
374
375    /// Converts a mutable `SmallString` into a mutable byte slice.
376    ///
377    /// # Safety
378    /// The caller must ensure that the content of the slice remains valid UTF-8.
379    /// If this invariant is violated, it is Undefined Behavior.
380    #[inline(always)]
381    pub unsafe fn as_bytes_mut(&mut self) -> &mut [u8] {
382        unsafe { self.as_mut_str().as_bytes_mut() }
383    }
384}
385
386// --- Safety & Standard Traits ---
387
388// Manual implementation of Clone.
389// We must check `on_stack` to know which field to clone.
390impl<const N: usize> Clone for SmallString<N> {
391    fn clone(&self) -> Self {
392        unsafe {
393            if self.on_stack {
394                // Clone the Stack string
395                let stack_clone = (*self.data.stack).clone();
396                SmallString {
397                    on_stack: true,
398                    data: StringData {
399                        stack: ManuallyDrop::new(stack_clone),
400                    },
401                }
402            } else {
403                // Clone the Heap string
404                let heap_clone = (*self.data.heap).clone();
405                SmallString {
406                    on_stack: false,
407                    data: StringData {
408                        heap: ManuallyDrop::new(heap_clone),
409                    },
410                }
411            }
412        }
413    }
414}
415
416impl<const N: usize> Drop for SmallString<N> {
417    fn drop(&mut self) {
418        unsafe {
419            if self.on_stack {
420                ManuallyDrop::drop(&mut self.data.stack);
421            } else {
422                ManuallyDrop::drop(&mut self.data.heap);
423            }
424        }
425    }
426}
427
428impl<const N: usize> Default for SmallString<N> {
429    fn default() -> Self {
430        Self::new()
431    }
432}
433
434impl<const N: usize> fmt::Display for SmallString<N> {
435    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
436        fmt::Display::fmt(self.as_str(), f) // Delegate to str implementation
437    }
438}
439
440impl<const N: usize> fmt::Debug for SmallString<N> {
441    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
442        fmt::Debug::fmt(self.as_str(), f)
443    }
444}
445
446// Allow: let s: SmallString<32> = "Hello".into();
447impl<const N: usize> From<&str> for SmallString<N> {
448    fn from(s: &str) -> Self {
449        Self::from_str(s)
450    }
451}
452
453// Allow writing to it like a buffer: write!(s, "Val: {}", 42)
454impl<const N: usize> fmt::Write for SmallString<N> {
455    fn write_str(&mut self, s: &str) -> fmt::Result {
456        self.push_str(s);
457        Ok(())
458    }
459}
460
461// --- Equality (==) ---
462
463// Added `const M: usize` to allow comparing SmallStrings of different capacities.
464// e.g., SmallString<16> == SmallString<2>
465impl<const N: usize, const M: usize> PartialEq<SmallString<M>> for SmallString<N> {
466    fn eq(&self, other: &SmallString<M>) -> bool {
467        self.as_str() == other.as_str()
468    }
469}
470
471// Eq implies PartialEq<Self>, which is satisfied by the implementation above where M = N.
472impl<const N: usize> Eq for SmallString<N> {}
473
474// --- Equality with other types (unchanged) ---
475
476impl<const N: usize> PartialEq<str> for SmallString<N> {
477    fn eq(&self, other: &str) -> bool {
478        self.as_str() == other
479    }
480}
481
482// Allow: small_string == "hello" (where "hello" is &str)
483impl<'a, const N: usize> PartialEq<&'a str> for SmallString<N> {
484    fn eq(&self, other: &&'a str) -> bool {
485        self.as_str() == *other
486    }
487}
488
489impl<const N: usize> PartialEq<SmallString<N>> for &str {
490    fn eq(&self, other: &SmallString<N>) -> bool {
491        *self == other.as_str()
492    }
493}
494
495impl<const N: usize> PartialEq<String> for SmallString<N> {
496    fn eq(&self, other: &String) -> bool {
497        self.as_str() == other.as_str()
498    }
499}
500
501// --- Ordering (<, >) ---
502
503// FIXED: Added `const M: usize` here as well.
504impl<const N: usize, const M: usize> PartialOrd<SmallString<M>> for SmallString<N> {
505    fn partial_cmp(&self, other: &SmallString<M>) -> Option<Ordering> {
506        Some(self.as_str().cmp(other.as_str()))
507    }
508}
509
510impl<const N: usize> Ord for SmallString<N> {
511    fn cmp(&self, other: &Self) -> Ordering {
512        self.as_str().cmp(other.as_str())
513    }
514}
515
516// --- Hashing ---
517
518impl<const N: usize> Hash for SmallString<N> {
519    fn hash<H: Hasher>(&self, state: &mut H) {
520        // IMPORTANT: Hash the string slice, not the struct fields.
521        // This ensures hash("abc") == hash(SmallString::from("abc"))
522        self.as_str().hash(state);
523    }
524}
525
526// --- Borrowing --
527
528impl<const N: usize> Borrow<str> for SmallString<N> {
529    fn borrow(&self) -> &str {
530        self.as_str()
531    }
532}
533
534impl<const N: usize> BorrowMut<str> for SmallString<N> {
535    fn borrow_mut(&mut self) -> &mut str {
536        self.as_mut_str()
537    }
538}
539
540impl<const N: usize> AsRef<str> for SmallString<N> {
541    fn as_ref(&self) -> &str {
542        self.as_str()
543    }
544}
545
546impl<const N: usize> AsRef<[u8]> for SmallString<N> {
547    fn as_ref(&self) -> &[u8] {
548        self.as_bytes()
549    }
550}
551
552// --- Extend ---
553
554// Allow: let s: SmallString<16> = chars.collect();
555impl<const N: usize> FromIterator<char> for SmallString<N> {
556    fn from_iter<I: IntoIterator<Item = char>>(iter: I) -> Self {
557        let mut s = Self::new();
558        for c in iter {
559            s.push(c); // push handles spilling automatically
560        }
561        s
562    }
563}
564
565// Allow: let s: SmallString<16> = strings.collect();
566impl<'a, const N: usize> FromIterator<&'a str> for SmallString<N> {
567    fn from_iter<I: IntoIterator<Item = &'a str>>(iter: I) -> Self {
568        let mut s = Self::new();
569        for str_slice in iter {
570            s.push_str(str_slice);
571        }
572        s
573    }
574}
575
576// Allow: s.extend(chars)
577impl<const N: usize> Extend<char> for SmallString<N> {
578    fn extend<I: IntoIterator<Item = char>>(&mut self, iter: I) {
579        for c in iter {
580            self.push(c);
581        }
582    }
583}
584
585// Allow: s.extend(strings)
586impl<'a, const N: usize> Extend<&'a str> for SmallString<N> {
587    fn extend<I: IntoIterator<Item = &'a str>>(&mut self, iter: I) {
588        for str_slice in iter {
589            self.push_str(str_slice);
590        }
591    }
592}
593
594// --- Pop, Truncate ---
595
596impl<const N: usize> SmallString<N> {
597    /// Removes the last character from the string buffer and returns it.
598    /// Returns None if the string is empty.
599    #[inline(always)]
600    pub fn pop(&mut self) -> Option<char> {
601        unsafe {
602            if self.on_stack {
603                (*self.data.stack).pop()
604            } else {
605                (*self.data.heap).pop()
606            }
607        }
608    }
609
610    /// Shortens this String to the specified length.
611    /// If new_len >= current length, this does nothing.
612    /// Panics if new_len does not lie on a char boundary.
613    #[inline(always)]
614    pub fn truncate(&mut self, new_len: usize) {
615        unsafe {
616            if self.on_stack {
617                (*self.data.stack).truncate(new_len);
618            } else {
619                (*self.data.heap).truncate(new_len);
620            }
621        }
622    }
623
624    /// Returns the total capacity (in bytes) of the string.
625    #[inline(always)]
626    pub fn capacity(&self) -> usize {
627        unsafe {
628            if self.on_stack {
629                N // Stack capacity is fixed at N
630            } else {
631                (*self.data.heap).capacity()
632            }
633        }
634    }
635}
636
637// --- Reserve ---
638
639impl<const N: usize> SmallString<N> {
640    /// Ensures that this string has at least the specified capacity.
641    /// If the request exceeds N, this forces a spill to the heap immediately.
642    #[inline(always)]
643    pub fn reserve(&mut self, additional: usize) {
644        unsafe {
645            if self.on_stack {
646                let len = self.len();
647                if len + additional > N {
648                    // Force spill logic
649                    // We cheat slightly by calling spill with an empty string,
650                    // but we modify the logic to respect `capacity`.
651
652                    // 1. Copy Stack
653                    let stack_bytes = self.data.stack.as_bytes();
654
655                    // 2. Alloc Heap with requested size
656                    let cap = std::cmp::max(len + additional, N * 2);
657                    let mut heap_vec = Vec::with_capacity(cap);
658                    heap_vec.extend_from_slice(stack_bytes);
659
660                    let new_heap = String::from_utf8_unchecked(heap_vec);
661
662                    // 3. Switch
663                    ManuallyDrop::drop(&mut self.data.stack);
664                    self.data.heap = ManuallyDrop::new(new_heap);
665                    self.on_stack = false;
666                }
667                // Else: it fits on stack, do nothing.
668            } else {
669                (*self.data.heap).reserve(additional);
670            }
671        }
672    }
673}
674
675// -- u8 --
676
677impl<const N: usize> SmallString<N> {
678    /// Converts a vector of bytes to a SmallString.
679    ///
680    /// If the bytes are not valid UTF-8, this returns an error.
681    /// If valid, it attempts to store them on the Stack if they fit.
682    pub fn from_utf8(vec: Vec<u8>) -> Result<Self, std::string::FromUtf8Error> {
683        // 1. Check UTF-8 validity using std helper
684        let s_std = String::from_utf8(vec)?;
685
686        // 2. Try to fit on Stack
687        if s_std.len() <= N {
688            let mut small = Self::new();
689            small.push_str(&s_std);
690            Ok(small)
691        } else {
692            // 3. Keep on Heap
693            Ok(Self {
694                on_stack: false,
695                data: StringData {
696                    heap: ManuallyDrop::new(s_std),
697                },
698            })
699        }
700    }
701
702    /// Converts a byte slice to a SmallString.
703    pub fn from_utf8_lossy(bytes: &[u8]) -> Self {
704        let cow = String::from_utf8_lossy(bytes);
705        match cow {
706            // It was already valid utf8 and borrowed
707            std::borrow::Cow::Borrowed(s) => Self::from(s),
708            // It had invalid chars and was replaced/allocated
709            std::borrow::Cow::Owned(s) => {
710                if s.len() <= N {
711                    Self::from(s.as_str())
712                } else {
713                    Self {
714                        on_stack: false,
715                        data: StringData {
716                            heap: ManuallyDrop::new(s),
717                        },
718                    }
719                }
720            }
721        }
722    }
723}
724
725// -- interoperability --
726
727impl<const N: usize> SmallString<N> {
728    /// Consumes the SmallString and returns a heap-allocated `std::string::String`.
729    ///
730    /// If already on the heap, this is free (zero allocation).
731    /// If on the stack, this allocates a new String.
732    pub fn into_string(self) -> String {
733        // Prevent Drop from running, we are taking ownership
734        let mut this = ManuallyDrop::new(self);
735
736        unsafe {
737            if this.on_stack {
738                let stack_str = &*this.data.stack;
739                stack_str.as_str().to_string()
740            } else {
741                // Take ownership of the heap string
742                ManuallyDrop::take(&mut this.data.heap)
743            }
744        }
745    }
746
747    /// Consumes the SmallString and returns a `Vec<u8>`.
748    pub fn into_bytes(self) -> Vec<u8> {
749        self.into_string().into_bytes()
750    }
751}
752
753// -- retian --
754
755impl<const N: usize> SmallString<N> {
756    /// Retains only the characters specified by the predicate.
757    /// In other words, remove all characters `c` such that `f(c)` returns `false`.
758    pub fn retain<F>(&mut self, mut f: F)
759    where
760        F: FnMut(char) -> bool,
761    {
762        unsafe {
763            if self.on_stack {
764                // heapless::String doesn't implement retain directly.
765                // We simulate it by rebuilding the string in-place (simplest safe approach)
766                // or doing a "scan and shift".
767                // Since N is small, rebuilding is acceptable overhead.
768
769                let stack_str = &mut *self.data.stack;
770                // Note: This is an O(N) approach using a temporary buffer strategy
771                // implicitly via string manipulation, or we simply rely on the fact
772                // that we can't easily mutate the private buffer of heapless.
773
774                // Safe Workaround: Copy chars that pass to a temp buffer, then clear and push back.
775                // Since N is small (stack), this copy is cheap.
776                let mut temp: heapless::String<N> = heapless::String::new();
777                for c in stack_str.chars() {
778                    if f(c) {
779                        match temp.push(c) {
780                            Ok(()) => continue,
781                            Err(_) => unreachable!("temp string capacity check failed in push"),
782                        }
783                    }
784                }
785                *stack_str = temp;
786            } else {
787                (*self.data.heap).retain(f);
788            }
789        }
790    }
791}
792
793// -- shrink --
794
795impl<const N: usize> SmallString<N> {
796    /// Demands that the underlying buffer releases any extra capacity.
797    /// Only affects the Heap state. Stack state size is fixed at `N`.
798    pub fn shrink_to_fit(&mut self) {
799        unsafe {
800            if !self.on_stack {
801                (*self.data.heap).shrink_to_fit();
802            }
803        }
804    }
805}
806
807#[cfg(test)]
808mod string_basic_tests {
809
810    use super::*;
811    use std::borrow::Borrow;
812    use std::collections::HashSet;
813    use std::collections::hash_map::DefaultHasher;
814    use std::fmt::Write; // Required for write! macro tests
815    use std::hash::{Hash, Hasher};
816
817    #[test]
818    fn test_string_traits_borrow() {
819        use std::borrow::{Borrow, BorrowMut};
820        let mut s: SmallString<16> = SmallString::from("abc");
821
822        // Test Borrow<str>
823        let b: &str = s.borrow();
824        assert_eq!(b, "abc");
825
826        // Test BorrowMut<str>
827        let b_mut: &mut str = s.borrow_mut();
828        b_mut.make_ascii_uppercase();
829        assert_eq!(s.as_str(), "ABC");
830    }
831
832    #[test]
833    fn test_string_stack_ops_basic() {
834        let mut s: SmallString<16> = SmallString::new();
835
836        assert!(s.is_on_stack());
837        assert!(s.is_empty());
838
839        s.push_str("Hello");
840        assert_eq!(s.len(), 5);
841        assert_eq!(s.as_str(), "Hello");
842        assert!(s.is_on_stack());
843
844        s.push(' ');
845        s.push_str("World");
846        assert_eq!(s.len(), 11);
847        assert_eq!(&*s, "Hello World"); // Test Deref
848        assert!(s.is_on_stack());
849    }
850
851    // --- 2. Exact Boundary Tests ---
852    #[test]
853    fn test_string_spill_trigger_on_exact_capacity() {
854        let mut s: SmallString<5> = SmallString::new();
855
856        // Fill exactly to capacity
857        s.push_str("12345");
858
859        assert_eq!(s.len(), 5);
860        assert!(s.is_on_stack(), "Should remain on stack at exact capacity");
861
862        // Push one more char -> Spill
863        s.push('6');
864        assert!(!s.is_on_stack(), "Should spill after N+1");
865        assert_eq!(s.as_str(), "123456");
866    }
867
868    // --- 3. Spill via push_str (Multi-char) ---
869    #[test]
870    fn test_string_spill_trigger_on_push_str() {
871        let mut s: SmallString<4> = SmallString::new();
872        s.push_str("Hi"); // Len 2 (Stack)
873
874        // Push string that exceeds remaining space AND total capacity
875        s.push_str(" there"); // "Hi there" = 8 chars
876
877        assert!(!s.is_on_stack());
878        assert_eq!(s.as_str(), "Hi there");
879        assert_eq!(s.len(), 8);
880    }
881
882    // --- 4. Spill via push (Single char) ---
883    #[test]
884    fn test_string_spill_trigger_on_push_char() {
885        let mut s: SmallString<3> = SmallString::new();
886        s.push('A');
887        s.push('B');
888        s.push('C'); // Full (3/3)
889
890        s.push('D'); // Spill
891
892        assert!(!s.is_on_stack());
893        assert_eq!(s.as_str(), "ABCD");
894    }
895
896    // --- 5. UTF-8 & Emojis (CRITICAL for unsafe blocks) ---
897    #[test]
898    fn test_string_spill_trigger_on_multibyte_char() {
899        // Emojis are 4 bytes each.
900        // Capacity 6: Can hold 1 emoji (4 bytes) + 2 bytes.
901        // Cannot hold 2 emojis (8 bytes).
902        let mut s: SmallString<6> = SmallString::new();
903
904        s.push('🦀'); // 4 bytes
905        assert!(s.is_on_stack());
906        assert_eq!(s.len(), 4);
907
908        // Push another emoji. 4 + 4 = 8 bytes. Spill required.
909        // This tests the `push(char)` spill path with multibyte chars.
910        s.push('🚀');
911
912        assert!(!s.is_on_stack());
913        assert_eq!(s.as_str(), "🦀🚀");
914        assert_eq!(s.len(), 8);
915    }
916
917    #[test]
918    fn test_string_spill_trigger_on_multibyte_str() {
919        // Test `push_str` splitting logic (though we copy fully)
920        let mut s: SmallString<5> = SmallString::new();
921        s.push_str("hi");
922
923        // Push mixed ascii and unicode that causes spill
924        // "hi" (2) + "_👋" (1 + 4) = 7 bytes total
925        s.push_str("_👋");
926
927        assert!(!s.is_on_stack());
928        assert_eq!(s.as_str(), "hi_👋");
929    }
930
931    // --- 6. Formatting Macro (fmt::Write) ---
932    #[test]
933    fn test_string_traits_fmt_write() {
934        let mut s: SmallString<16> = SmallString::new();
935
936        // Should fit on stack
937        write!(s, "Value: {}", 100).unwrap();
938        assert_eq!(s.as_str(), "Value: 100");
939        assert!(s.is_on_stack());
940
941        // Should cause spill
942        write!(s, " and a very long suffix to force spill").unwrap();
943        assert!(!s.is_on_stack());
944        assert!(s.contains("Value: 100"));
945        assert!(s.contains("suffix"));
946    }
947
948    // --- 7. Large Allocation Strategy ---
949    #[test]
950    fn test_string_spill_trigger_on_large_growth() {
951        let mut s: SmallString<4> = SmallString::new();
952        s.push_str("12");
953
954        // Push a huge string immediately
955        // The spill logic `max(needed, N*2)` should handle this without crashing
956        let huge_chunk = "a".repeat(100);
957        s.push_str(&huge_chunk);
958
959        assert!(!s.is_on_stack());
960        assert_eq!(s.len(), 102);
961        assert!(s.ends_with("aaaa"));
962    }
963
964    // --- 8. Zero Capacity Edge Case ---
965    #[test]
966    fn test_string_any_storage_zero_capacity() {
967        // A SmallString that holds nothing on the stack
968        let mut s: SmallString<0> = SmallString::new();
969
970        assert!(s.is_on_stack()); // Technically starts on "stack" (empty)
971
972        s.push('a');
973        assert!(!s.is_on_stack()); // Immediate spill
974        assert_eq!(s.as_str(), "a");
975    }
976
977    // --- 9. Clear & Reuse ---
978    #[test]
979    fn test_string_any_storage_clear_reuse() {
980        let mut s: SmallString<4> = SmallString::new();
981
982        // Stack -> Clear -> Stack
983        s.push_str("abc");
984        s.clear();
985        assert!(s.is_empty());
986        assert!(s.is_on_stack());
987        s.push_str("xyz");
988        assert_eq!(s.as_str(), "xyz");
989
990        // Stack -> Spill -> Clear -> Heap (Remains on heap usually)
991        s.push_str("12345"); // Spill
992        assert!(!s.is_on_stack());
993
994        s.clear();
995        assert!(s.is_empty());
996        assert!(!s.is_on_stack()); // Implementation detail: keeps heap allocation
997
998        // Reuse heap allocation
999        s.push_str("HeapReuse");
1000        assert_eq!(s.as_str(), "HeapReuse");
1001    }
1002
1003    // --- 10. Deref Methods ---
1004    #[test]
1005    fn test_string_traits_deref_methods() {
1006        let s: SmallString<10> = SmallString::from("hello");
1007
1008        // These methods come from standard `str`, working via Deref
1009        assert!(s.starts_with("he"));
1010        assert!(s.contains("ll"));
1011        assert_eq!(s.to_uppercase(), "HELLO"); // Returns a new Std String
1012        assert_eq!(s.find('o'), Some(4));
1013    }
1014
1015    #[test]
1016    fn test_string_traits_clone_on_stack() {
1017        // N=16. "Hello" (5 bytes) fits on stack.
1018        let mut original: SmallString<16> = SmallString::new();
1019        original.push_str("Hello");
1020
1021        assert!(original.is_on_stack());
1022
1023        // Clone it
1024        let mut copy = original.clone();
1025
1026        // Verify state
1027        assert!(copy.is_on_stack());
1028        assert_eq!(original.as_str(), copy.as_str());
1029
1030        // Modify Copy -> Ensure Original is untouched
1031        copy.push_str(" World");
1032
1033        assert_eq!(original.as_str(), "Hello");
1034        assert_eq!(copy.as_str(), "Hello World");
1035    }
1036
1037    #[test]
1038    fn test_string_traits_clone_on_heap() {
1039        // N=4. "Hello" (5 bytes) forces a spill to Heap.
1040        let mut original: SmallString<4> = SmallString::new();
1041        original.push_str("Hello");
1042
1043        assert!(!original.is_on_stack());
1044
1045        // Clone it
1046        let mut copy = original.clone();
1047
1048        // Verify state
1049        assert!(!copy.is_on_stack());
1050        assert_eq!(original.as_str(), copy.as_str());
1051
1052        // Modify Copy -> Ensure Original is untouched
1053        copy.push_str(" World");
1054
1055        assert_eq!(original.as_str(), "Hello");
1056        assert_eq!(copy.as_str(), "Hello World");
1057    }
1058
1059    // --- Helper to verify Hashing consistency ---
1060    fn calculate_hash<T: Hash>(t: &T) -> u64 {
1061        let mut s = DefaultHasher::new();
1062        t.hash(&mut s);
1063        s.finish()
1064    }
1065
1066    // --- 1. Equality & Ordering Tests ---
1067    #[test]
1068    fn test_string_traits_equality() {
1069        // Stack vs Stack
1070        let s1: SmallString<16> = SmallString::from("hello");
1071        let s2: SmallString<16> = SmallString::from("hello");
1072        let s3: SmallString<16> = SmallString::from("world");
1073        assert_eq!(s1, s2);
1074        assert_ne!(s1, s3);
1075
1076        // Heap vs Heap (N=2 forces spill)
1077        let h1: SmallString<2> = SmallString::from("hello");
1078        let h2: SmallString<2> = SmallString::from("hello");
1079        assert_eq!(h1, h2);
1080        assert!(!h1.is_on_stack());
1081
1082        // Stack vs Heap
1083        // s1 is on stack, h1 is on heap. Content is same.
1084        assert_eq!(s1, h1);
1085
1086        // Comparison with &str and String
1087        assert_eq!(s1, "hello");
1088        assert_eq!("hello", s1);
1089        assert_eq!(s1, String::from("hello"));
1090    }
1091
1092    #[test]
1093    fn test_string_traits_ordering() {
1094        let apple: SmallString<16> = SmallString::from("Apple");
1095        let banana: SmallString<16> = SmallString::from("Banana");
1096
1097        // "Apple" < "Banana"
1098        assert!(apple < banana);
1099
1100        // Sort a vector of SmallStrings
1101        let mut list = vec![banana.clone(), apple.clone()];
1102        list.sort();
1103
1104        assert_eq!(list[0], "Apple");
1105        assert_eq!(list[1], "Banana");
1106    }
1107
1108    // --- 2. Hashing Tests ---
1109    #[test]
1110    fn test_string_traits_hashing() {
1111        let s_stack: SmallString<16> = SmallString::from("testing");
1112        let s_heap: SmallString<2> = SmallString::from("testing"); // Spills
1113        let s_std: String = String::from("testing");
1114        let s_str: &str = "testing";
1115
1116        // IMPORTANT: The hash must be identical to standard Rust strings
1117        // for HashMap interoperability.
1118        let h1 = calculate_hash(&s_stack);
1119        let h2 = calculate_hash(&s_heap);
1120        let h3 = calculate_hash(&s_std);
1121        let h4 = calculate_hash(&s_str);
1122
1123        assert_eq!(h1, h2, "Stack and Heap hashing differ!");
1124        assert_eq!(h1, h3, "SmallString hash differs from String hash!");
1125        assert_eq!(h1, h4, "SmallString hash differs from &str hash!");
1126
1127        // Verify use in HashSet
1128        let mut set = HashSet::new();
1129        set.insert(s_stack.clone());
1130
1131        assert!(set.contains("testing")); // Look up using &str
1132        assert!(set.contains(s_heap.as_str())); // Look up using Heap SmallString
1133    }
1134
1135    // --- 3. Borrow & AsRef Tests ---
1136    #[test]
1137    fn test_string_traits_borrow_as_ref() {
1138        let s: SmallString<16> = SmallString::from("hello");
1139
1140        // Function expecting &str
1141        fn takes_str(_: &str) {}
1142        takes_str(s.as_ref());
1143        takes_str(s.borrow());
1144
1145        // Function expecting AsRef<str>
1146        fn takes_as_ref<T: AsRef<str>>(_: T) {}
1147        takes_as_ref(s); // Pass by value
1148    }
1149
1150    // --- 4. Iterator Tests (FromIterator / Extend) ---
1151    #[test]
1152    fn test_string_traits_from_iterator() {
1153        // Collect chars -> Stack
1154        let chars = vec!['a', 'b', 'c'];
1155        let s_stack: SmallString<16> = chars.into_iter().collect();
1156        assert_eq!(s_stack, "abc");
1157        assert!(s_stack.is_on_stack());
1158
1159        // Collect chars -> Heap
1160        let many_chars = vec!['a'; 100];
1161        let s_heap: SmallString<16> = many_chars.into_iter().collect();
1162        assert_eq!(s_heap.len(), 100);
1163        assert!(!s_heap.is_on_stack());
1164
1165        // Collect strings
1166        let strings = vec!["Hello", " ", "World"];
1167        let s_str: SmallString<32> = strings.into_iter().collect();
1168        assert_eq!(s_str, "Hello World");
1169    }
1170
1171    #[test]
1172    fn test_string_traits_extend() {
1173        let mut s: SmallString<4> = SmallString::new();
1174
1175        // Extend a little (Stack)
1176        s.extend(vec!['H', 'i']);
1177        assert_eq!(s, "Hi");
1178        assert!(s.is_on_stack());
1179
1180        // Extend a lot (Spill)
1181        s.extend(vec!['!'; 20]);
1182        assert!(!s.is_on_stack());
1183        assert_eq!(s.len(), 22);
1184    }
1185
1186    // --- 5. Manipulation Tests (Pop / Truncate) ---
1187    #[test]
1188    fn test_string_any_storage_pop() {
1189        // Stack Pop
1190        let mut s: SmallString<16> = SmallString::from("abc");
1191        assert_eq!(s.pop(), Some('c'));
1192        assert_eq!(s.pop(), Some('b'));
1193        assert_eq!(s, "a");
1194
1195        // Heap Pop
1196        let mut h: SmallString<2> = SmallString::from("abc"); // Spills
1197        assert!(!h.is_on_stack());
1198        assert_eq!(h.pop(), Some('c'));
1199        assert_eq!(h, "ab");
1200
1201        // Empty Pop
1202        let mut empty: SmallString<4> = SmallString::new();
1203        assert_eq!(empty.pop(), None);
1204    }
1205
1206    #[test]
1207    fn test_string_any_storage_truncate_v2() {
1208        // Stack Truncate
1209        let mut s: SmallString<16> = SmallString::from("Hello World");
1210        s.truncate(5);
1211        assert_eq!(s, "Hello");
1212        assert_eq!(s.len(), 5);
1213
1214        // Truncate to larger size (Should do nothing)
1215        s.truncate(100);
1216        assert_eq!(s, "Hello");
1217
1218        // Heap Truncate
1219        let mut h: SmallString<2> = SmallString::from("Hello World"); // Spills
1220        assert!(!h.is_on_stack());
1221        h.truncate(2);
1222        assert_eq!(h, "He");
1223    }
1224
1225    #[test]
1226    #[should_panic]
1227    fn test_string_any_storage_truncate_panic() {
1228        let mut s: SmallString<16> = SmallString::from("🦀"); // 4 bytes
1229        // Truncating inside a multibyte char panics in std, should panic here too
1230        s.truncate(2);
1231    }
1232
1233    // --- 6. Reserve (Spill Control) ---
1234    #[test]
1235    fn test_string_any_storage_reserve() {
1236        // Case 1: Reserve fits in Stack
1237        let mut s: SmallString<16> = SmallString::new();
1238        s.push_str("Hi");
1239        s.reserve(4); // 2 + 4 = 6 <= 16.
1240        assert!(s.is_on_stack());
1241
1242        // Case 2: Reserve forces Spill
1243        // We are at len 2. We request 20 more. Total 22 > 16.
1244        // This should force a move to heap.
1245        s.reserve(20);
1246
1247        assert!(!s.is_on_stack());
1248        assert_eq!(s.as_str(), "Hi");
1249        assert!(s.capacity() >= 22); // Correct: 2 + 20
1250
1251        // Case 3: Reserve on existing Heap
1252        // We request 100 MORE bytes than current length (2).
1253        // Total required = 102.
1254        s.reserve(100);
1255
1256        // FIX: Check against 102, not 122.
1257        assert!(s.capacity() >= 102);
1258    }
1259
1260    #[test]
1261    fn test_string_any_storage_into_string() {
1262        // Stack -> String
1263        let s_stack: SmallString<16> = SmallString::from("stack");
1264        let std_str = s_stack.into_string();
1265        assert_eq!(std_str, "stack");
1266        // Verify type is actually String
1267        let _: String = std_str;
1268
1269        // Heap -> String (Zero cost move)
1270        let s_heap: SmallString<2> = SmallString::from("heap");
1271        let std_str2 = s_heap.into_string();
1272        assert_eq!(std_str2, "heap");
1273    }
1274
1275    #[test]
1276    fn test_string_any_storage_from_utf8() {
1277        let bytes = vec![104, 101, 108, 108, 111]; // "hello"
1278
1279        // Fits on Stack
1280        let s: SmallString<16> = SmallString::from_utf8(bytes.clone()).unwrap();
1281        assert!(s.is_on_stack());
1282        assert_eq!(s, "hello");
1283
1284        // Force Heap
1285        let s2: SmallString<2> = SmallString::from_utf8(bytes).unwrap();
1286        assert!(!s2.is_on_stack());
1287        assert_eq!(s2, "hello");
1288
1289        // Invalid UTF-8
1290        let invalid = vec![0, 159, 146, 150];
1291        assert!(SmallString::<16>::from_utf8(invalid).is_err());
1292    }
1293
1294    #[test]
1295    fn test_string_any_storage_retain() {
1296        // Stack Retain
1297        let mut s: SmallString<16> = SmallString::from("AbCdEf");
1298        s.retain(|c| c.is_lowercase());
1299        assert_eq!(s, "bdf");
1300        assert!(s.is_on_stack());
1301
1302        // Heap Retain
1303        let mut h: SmallString<2> = SmallString::from("AbCdEf"); // Spills
1304        h.retain(|c| c.is_uppercase());
1305        assert_eq!(h, "ACE");
1306        assert!(!h.is_on_stack());
1307    }
1308
1309    #[test]
1310    fn test_string_any_storage_into_bytes() {
1311        let s: SmallString<16> = SmallString::from("ABC");
1312        let bytes = s.into_bytes();
1313        assert_eq!(bytes, vec![65, 66, 67]);
1314    }
1315
1316    #[test]
1317    fn test_string_any_storage_from_utf8_lossy() {
1318        let bytes = b"hello \xF0\x90\x80world"; // Invalid UTF-8
1319        let s: SmallString<16> = SmallString::from_utf8_lossy(bytes);
1320        assert!(s.contains("hello "));
1321        assert!(s.contains("world"));
1322        assert!(s.is_on_stack());
1323
1324        let huge_invalid = b"a".repeat(100);
1325        let s2: SmallString<16> = SmallString::from_utf8_lossy(&huge_invalid);
1326        assert!(!s2.is_on_stack());
1327    }
1328
1329    #[test]
1330    fn test_string_any_storage_as_bytes_mut() {
1331        let mut s: SmallString<16> = SmallString::from("abc");
1332        unsafe {
1333            let bytes = s.as_bytes_mut();
1334            bytes[0] = b'z';
1335        }
1336        assert_eq!(s, "zbc");
1337    }
1338
1339    #[test]
1340    fn test_string_traits_debug_display() {
1341        let h: SmallString<2> = SmallString::from("a");
1342        let debug = format!("{:?}", h);
1343        assert_eq!(debug, "\"a\"");
1344        let display = format!("{}", h);
1345        assert_eq!(display, "a");
1346    }
1347
1348    #[test]
1349    fn test_string_any_storage_truncate() {
1350        // truncate stack
1351        let mut s: SmallString<16> = SmallString::from("hello");
1352        s.truncate(2);
1353        assert_eq!(s, "he");
1354
1355        // truncate heap
1356        let mut h: SmallString<2> = SmallString::from("abc");
1357        h.truncate(1);
1358        assert_eq!(h, "a");
1359    }
1360
1361    #[test]
1362    fn test_string_any_storage_deref_mut() {
1363        let mut s_mut = SmallString::<16>::from("abc");
1364        s_mut.as_mut_str().make_ascii_uppercase();
1365        assert_eq!(s_mut, "ABC");
1366
1367        let mut h: SmallString<1> = SmallString::from_str("abc");
1368        h.as_mut_str().make_ascii_uppercase();
1369        assert_eq!(h, "ABC");
1370    }
1371
1372    #[test]
1373    fn test_string_any_storage_partial_eq_variants() {
1374        let h: SmallString<1> = SmallString::from_str("abc");
1375        assert!(h == "abc");
1376        let s_ref = "abc";
1377        assert!(h == s_ref);
1378    }
1379
1380    #[test]
1381    fn test_string_any_storage_extend_char() {
1382        let mut h: SmallString<1> = SmallString::from_str("abc");
1383        h.extend(['!', '?'].iter().cloned());
1384        assert_eq!(h, "abc!?");
1385    }
1386
1387    #[test]
1388    fn test_string_any_storage_from_utf8_long() {
1389        let long_bytes = b"a".repeat(100);
1390        let s_long = SmallString::<16>::from_utf8(long_bytes).unwrap();
1391        assert!(!s_long.is_on_stack());
1392    }
1393}
1394
1395#[cfg(test)]
1396mod string_coverage_tests {
1397    use super::*;
1398
1399    #[test]
1400    fn test_any_string_trait_std_string_implementation() {
1401        let mut s = String::from("abc");
1402        let any: &mut dyn AnyString = &mut s;
1403        assert_eq!(any.as_str(), "abc");
1404        assert_eq!(any.len(), 3);
1405        assert!(!any.is_empty());
1406        any.push('d');
1407        any.push_str("ef");
1408        assert_eq!(any.as_str(), "abcdef");
1409        assert_eq!(any.pop(), Some('f'));
1410        any.truncate(3);
1411        assert_eq!(any.as_str(), "abc");
1412        any.clear();
1413        assert!(any.is_empty());
1414    }
1415
1416    #[test]
1417    fn test_any_string_trait_small_string_implementation() {
1418        let mut s: SmallString<4> = SmallString::new();
1419        let any: &mut dyn AnyString = &mut s;
1420        any.push_str("abc");
1421        assert_eq!(any.len(), 3);
1422        any.push('d');
1423        assert_eq!(any.as_str(), "abcd");
1424        any.pop();
1425        any.truncate(1);
1426        any.clear();
1427        assert!(any.is_empty());
1428    }
1429
1430    #[test]
1431    fn test_small_string_heap_storage_utf8_validity_and_retain() {
1432        let mut s: SmallString<2> = SmallString::new();
1433        s.push_str("abc"); // heap
1434        assert!(!s.is_on_stack());
1435        assert!(s.capacity() >= 2);
1436
1437        // from_utf8 heap
1438        let long_vec = vec![b'a'; 10];
1439        let s2 = SmallString::<2>::from_utf8(long_vec).unwrap();
1440        assert!(!s2.is_on_stack());
1441
1442        // from_utf8_lossy heap
1443        let s3 = SmallString::<2>::from_utf8_lossy(&[b'a'; 10]);
1444        assert!(!s3.is_on_stack());
1445
1446        // retain heap
1447        let mut s4: SmallString<2> = SmallString::from("abcde");
1448        s4.retain(|c| c != 'b');
1449        assert_eq!(s4.as_str(), "acde");
1450
1451        // shrink_to_fit heap
1452        s4.shrink_to_fit();
1453    }
1454
1455    #[test]
1456    fn test_small_string_formatting_comparison_and_extension() {
1457        let mut s: SmallString<4> = SmallString::from("abc");
1458
1459        // Display / Debug
1460        assert_eq!(format!("{}", s), "abc");
1461        assert_eq!(format!("{:?}", s), "\"abc\"");
1462
1463        // PartialEq / PartialOrd
1464        let s2 = SmallString::<8>::from("abc");
1465        assert_eq!(s, s2);
1466        assert_eq!(s, "abc");
1467        assert_eq!("abc", s);
1468        assert_eq!(s, String::from("abc"));
1469
1470        let s3 = SmallString::<4>::from("abd");
1471        assert!(s < s3);
1472        assert_eq!(s.cmp(&s3), std::cmp::Ordering::Less);
1473
1474        // Hash
1475        let mut h = std::collections::hash_map::DefaultHasher::new();
1476        s.hash(&mut h);
1477
1478        // Borrow / AsRef
1479        let b: &str = s.borrow();
1480        assert_eq!(b, "abc");
1481        let b_mut: &mut str = s.borrow_mut();
1482        b_mut.make_ascii_uppercase();
1483        assert_eq!(s.as_str(), "ABC");
1484
1485        let r: &str = s.as_ref();
1486        assert_eq!(r, "ABC");
1487        let r_bytes: &[u8] = s.as_ref();
1488        assert_eq!(r_bytes, b"ABC");
1489
1490        // Extend / FromIterator
1491        let mut s4: SmallString<4> = SmallString::new();
1492        s4.extend(['a', 'b']);
1493        s4.extend(["cd", "ef"]); // heap
1494        assert_eq!(s4.as_str(), "abcdef");
1495
1496        let s5 = SmallString::<4>::from_iter(['x', 'y']);
1497        assert_eq!(s5.as_str(), "xy");
1498        let s6 = SmallString::<4>::from_iter(["hi", "ho"]);
1499        assert_eq!(s6.as_str(), "hiho");
1500    }
1501
1502    #[test]
1503    fn test_small_string_fmt_write_trait() {
1504        use std::fmt::Write;
1505        let mut s: SmallString<4> = SmallString::new();
1506        write!(s, "{}", 12345).unwrap();
1507        assert_eq!(s.as_str(), "12345");
1508    }
1509}