Skip to main content

small_collections/
small_string.rs

1extern crate alloc;
2
3#[derive(Clone, Debug)]
4#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
5pub enum SmallString<const N: usize> {
6    Inline(heapless::String<N>),
7    Heap(alloc::string::String),
8}
9
10impl<const N: usize> SmallString<N> {
11    #[inline(always)]
12    pub fn new() -> Self {
13        SmallString::Inline(heapless::String::new())
14    }
15
16    /// Copy the content of an `&str` into a new [`SmallString`].
17    /// The data is stored inline if possible.
18    #[inline(always)]
19    pub fn from_str(s: &str) -> Self {
20        let mut str = Self::new();
21        str.push_str(s);
22        str
23    }
24
25    /// Creates a [`SmallString`] from an already heap-allocateed String.
26    #[inline(always)]
27    pub fn from_string(s: alloc::string::String) -> Self {
28        Self::Heap(s)
29    }
30
31    #[inline(always)]
32    pub fn from_small_string<const M: usize>(input: SmallString<M>) -> Self {
33        match input {
34            SmallString::Heap(s) if s.len() <= N => SmallString::from_str(&s),
35            SmallString::Heap(s) => SmallString::Heap(s),
36            SmallString::Inline(s) => SmallString::from_str(s.as_str()),
37        }
38    }
39
40    /// Converts a `Vec` of bytes to a [`SmallString`].
41    ///
42    /// If the bytes are not valid UTF-8, this returns an error.
43    /// If valid, it attempts to store them inline if they fit.
44    #[inline(always)]
45    pub fn from_utf8(bytes: alloc::vec::Vec<u8>) -> Result<Self, alloc::string::FromUtf8Error> {
46        Ok(Self::Heap(alloc::string::String::from_utf8(bytes)?))
47    }
48
49    /// Converts a slice of bytes to a [`SmallString`].
50    ///
51    /// If the bytes are not valid UTF-8, this returns an error.
52    /// If valid, it attempts to store them inline if they fit.
53    #[inline(always)]
54    pub fn from_utf8_slice(bytes: &[u8]) -> Result<Self, core::str::Utf8Error> {
55        Ok(Self::from_str(core::str::from_utf8(bytes)?))
56    }
57
58    /// Converts a slice of bytes to a [`SmallString`], replacing invalid characters.
59    #[inline(always)]
60    pub fn from_utf8_lossy(bytes: &[u8]) -> Self {
61        // TODO: should we move back to inline if str.len() allows it but str is owned?
62        let str = alloc::string::String::from_utf8_lossy(bytes);
63        match str {
64            alloc::borrow::Cow::Borrowed(borrowed) => Self::from_str(borrowed),
65            alloc::borrow::Cow::Owned(owned) => Self::from_string(owned),
66        }
67    }
68
69    /// Returns `true` if the string is currently storing data inline (on the stack).
70    #[inline(always)]
71    pub fn is_inline(&self) -> bool {
72        matches!(self, SmallString::Inline(_))
73    }
74
75    /// Returns the total capacity (in bytes) of the string.
76    #[inline(always)]
77    pub fn capacity(&self) -> usize {
78        match self {
79            SmallString::Inline(_) => N,
80            SmallString::Heap(str) => str.capacity(),
81        }
82    }
83
84    /// Returns the length of this [`SmallString`] in bytes, not [`char`]s or graphemes.
85    #[inline(always)]
86    pub fn len(&self) -> usize {
87        match self {
88            SmallString::Inline(str) => str.len(),
89            SmallString::Heap(str) => str.len(),
90        }
91    }
92
93    /// Returns `true` if this [`SmallString`] has a length of zero, and `false` otherwise.
94    #[inline(always)]
95    pub fn is_empty(&self) -> bool {
96        self.len() == 0
97    }
98
99    /// Truncates this [`SmallString`], removing all contents.
100    ///
101    /// While this means the [`SmallString`] will have a length of zero, it does not
102    /// touch its capacity.
103    #[inline(always)]
104    pub fn clear(&mut self) {
105        match self {
106            SmallString::Inline(str) => str.clear(),
107            SmallString::Heap(str) => str.clear(),
108        }
109    }
110
111    #[inline(always)]
112    pub fn as_str(&self) -> &str {
113        // Leverage Deref
114        self
115    }
116
117    #[inline(always)]
118    pub fn as_mut_str(&mut self) -> &mut str {
119        // Leverage DerefMut
120        self
121    }
122
123    /// Converts a [`SmallString`] into a byte slice.
124    #[inline(always)]
125    pub fn as_bytes(&self) -> &[u8] {
126        self.as_str().as_bytes()
127    }
128
129    /// # Safety
130    ///
131    /// The caller must ensure that the content of the slice remains valid UTF-8.
132    /// If this invariant is violated, it is Undefined Behavior.
133    #[inline(always)]
134    pub unsafe fn as_bytes_mut(&mut self) -> &mut [u8] {
135        unsafe { self.as_mut_str().as_bytes_mut() }
136    }
137
138    /// Removes the last character from the string buffer and returns it.
139    /// Returns None if the string is empty.
140    #[inline(always)]
141    pub fn pop(&mut self) -> Option<char> {
142        match self {
143            SmallString::Inline(str) => str.pop(),
144            SmallString::Heap(str) => str.pop(),
145        }
146    }
147
148    /// Shortens this String to the specified length.
149    /// If new_len >= current length, this does nothing.
150    ///
151    /// # Panics
152    ///
153    /// Panics if `new_len` does not lie on a [`char`] boundary.
154    #[inline(always)]
155    pub fn truncate(&mut self, new_len: usize) {
156        match self {
157            SmallString::Inline(str) => str.truncate(new_len),
158            SmallString::Heap(str) => str.truncate(new_len),
159        }
160    }
161
162    #[inline]
163    pub fn push_str(&mut self, input: &str) {
164        match self {
165            SmallString::Heap(str) => str.push_str(input),
166            SmallString::Inline(str) => {
167                if str.len() + input.len() <= N {
168                    // guaranteed success
169                    let _ = str.push_str(input);
170                } else {
171                    // we need to spill on the heap
172                    let new_capacity = core::cmp::max(str.len() + input.len(), N * 2);
173                    let mut heap_str = alloc::string::String::with_capacity(new_capacity);
174                    heap_str.push_str(str.as_str());
175                    heap_str.push_str(input);
176                    *self = SmallString::Heap(heap_str)
177                }
178            }
179        }
180    }
181
182    #[inline]
183    pub fn push(&mut self, ch: char) {
184        match self {
185            SmallString::Heap(str) => str.push(ch),
186            SmallString::Inline(str) => {
187                let char_len = ch.len_utf8();
188                if str.len() + char_len <= N {
189                    // guaranteed success
190                    let _ = str.push(ch);
191                } else {
192                    // we need to spill on the heap
193                    let new_capacity = core::cmp::max(str.len() + char_len, N * 2);
194                    let mut heap_str = alloc::string::String::with_capacity(new_capacity);
195                    heap_str.push_str(str.as_str());
196                    heap_str.push(ch);
197                    *self = SmallString::Heap(heap_str)
198                }
199            }
200        }
201    }
202
203    #[inline]
204    pub fn reserve(&mut self, additional: usize) {
205        match self {
206            SmallString::Heap(str) => str.reserve(additional),
207            SmallString::Inline(str) => {
208                if str.len() + additional > N {
209                    // spill to heap
210                    let new_capacity = core::cmp::max(str.len() + additional, N * 2);
211                    let mut heap_str = alloc::string::String::with_capacity(new_capacity);
212                    heap_str.push_str(str);
213                    *self = SmallString::Heap(heap_str)
214                }
215            }
216        }
217    }
218}
219
220impl<const N: usize> From<&str> for SmallString<N> {
221    #[inline(always)]
222    fn from(s: &str) -> Self {
223        Self::from_str(s)
224    }
225}
226
227impl<const N: usize> From<alloc::string::String> for SmallString<N> {
228    #[inline(always)]
229    fn from(s: alloc::string::String) -> Self {
230        Self::from_string(s)
231    }
232}
233
234impl<const N: usize> core::ops::Deref for SmallString<N> {
235    type Target = str;
236
237    #[inline(always)]
238    fn deref(&self) -> &Self::Target {
239        match self {
240            SmallString::Inline(str) => str.as_str(),
241            SmallString::Heap(str) => str.as_str(),
242        }
243    }
244}
245
246impl<const N: usize> core::ops::DerefMut for SmallString<N> {
247    #[inline(always)]
248    fn deref_mut(&mut self) -> &mut Self::Target {
249        match self {
250            SmallString::Inline(str) => str.as_mut_str(),
251            SmallString::Heap(str) => str.as_mut_str(),
252        }
253    }
254}
255
256impl<const N: usize> Default for SmallString<N> {
257    #[inline(always)]
258    fn default() -> Self {
259        Self::new()
260    }
261}
262
263impl<const N: usize> core::fmt::Display for SmallString<N> {
264    #[inline(always)]
265    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
266        core::fmt::Display::fmt(self.as_str(), f) // Delegate to str implementation
267    }
268}
269
270impl<const N: usize> core::fmt::Write for SmallString<N> {
271    #[inline(always)]
272    fn write_str(&mut self, s: &str) -> core::fmt::Result {
273        self.push_str(s);
274        Ok(())
275    }
276}
277
278impl<const N: usize, const M: usize> PartialEq<SmallString<M>> for SmallString<N> {
279    #[inline(always)]
280    fn eq(&self, other: &SmallString<M>) -> bool {
281        self.as_str() == other.as_str()
282    }
283}
284
285impl<const N: usize> Eq for SmallString<N> {}
286
287impl<const N: usize> PartialEq<str> for SmallString<N> {
288    #[inline(always)]
289    fn eq(&self, other: &str) -> bool {
290        self.as_str() == other
291    }
292}
293
294impl<'a, const N: usize> PartialEq<&'a str> for SmallString<N> {
295    #[inline(always)]
296    fn eq(&self, other: &&'a str) -> bool {
297        self.as_str() == *other
298    }
299}
300
301impl<const N: usize> PartialEq<SmallString<N>> for &str {
302    #[inline(always)]
303    fn eq(&self, other: &SmallString<N>) -> bool {
304        *self == other.as_str()
305    }
306}
307
308impl<const N: usize> PartialEq<alloc::string::String> for SmallString<N> {
309    #[inline(always)]
310    fn eq(&self, other: &alloc::string::String) -> bool {
311        self.as_str() == other.as_str()
312    }
313}
314
315impl<const N: usize, const M: usize> PartialOrd<SmallString<M>> for SmallString<N> {
316    #[inline(always)]
317    fn partial_cmp(&self, other: &SmallString<M>) -> Option<core::cmp::Ordering> {
318        Some(self.as_str().cmp(other.as_str()))
319    }
320}
321
322impl<const N: usize> Ord for SmallString<N> {
323    #[inline(always)]
324    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
325        self.as_str().cmp(other.as_str())
326    }
327}
328
329impl<const N: usize> core::hash::Hash for SmallString<N> {
330    #[inline(always)]
331    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
332        self.as_str().hash(state);
333    }
334}
335
336impl<const N: usize> core::borrow::Borrow<str> for SmallString<N> {
337    fn borrow(&self) -> &str {
338        self.as_str()
339    }
340}
341
342impl<const N: usize> core::borrow::BorrowMut<str> for SmallString<N> {
343    #[inline(always)]
344    fn borrow_mut(&mut self) -> &mut str {
345        self.as_mut_str()
346    }
347}
348
349impl<const N: usize> AsRef<str> for SmallString<N> {
350    fn as_ref(&self) -> &str {
351        self.as_str()
352    }
353}
354
355impl<const N: usize> AsRef<[u8]> for SmallString<N> {
356    #[inline(always)]
357    fn as_ref(&self) -> &[u8] {
358        self.as_bytes()
359    }
360}
361
362impl<const N: usize> FromIterator<char> for SmallString<N> {
363    #[inline]
364    fn from_iter<I: IntoIterator<Item = char>>(iter: I) -> Self {
365        let mut s = Self::new();
366        let iter = iter.into_iter();
367        s.reserve(iter.size_hint().0);
368        for c in iter {
369            s.push(c);
370        }
371        s
372    }
373}
374
375impl<'a, const N: usize> FromIterator<&'a str> for SmallString<N> {
376    #[inline]
377    fn from_iter<I: IntoIterator<Item = &'a str>>(iter: I) -> Self {
378        let mut s = Self::new();
379        let iter = iter.into_iter();
380        s.reserve(iter.size_hint().0);
381        for str_slice in iter {
382            s.push_str(str_slice);
383        }
384        s
385    }
386}
387
388impl<const N: usize> Extend<char> for SmallString<N> {
389    #[inline]
390    fn extend<I: IntoIterator<Item = char>>(&mut self, iter: I) {
391        let iter = iter.into_iter();
392        self.reserve(iter.size_hint().0);
393        for c in iter {
394            self.push(c);
395        }
396    }
397}
398
399impl<'a, const N: usize> Extend<&'a str> for SmallString<N> {
400    #[inline]
401    fn extend<I: IntoIterator<Item = &'a str>>(&mut self, iter: I) {
402        let iter = iter.into_iter();
403        self.reserve(iter.size_hint().0);
404        for str_slice in iter {
405            self.push_str(str_slice);
406        }
407    }
408}
409
410#[cfg(test)]
411mod tests {
412    use core::fmt::Write;
413
414    use crate::SmallString;
415
416    // -----------------------------------------------------------------------
417    // Existing baseline tests
418    // -----------------------------------------------------------------------
419
420    #[test]
421    fn test_basic_inline() {
422        let mut s: SmallString<16> = SmallString::new();
423        assert!(s.is_inline());
424        assert!(s.is_empty());
425        assert!(s.capacity() == 16);
426
427        s.push_str("Hello,");
428        assert_eq!(s.len(), 6);
429        assert_eq!(s.as_str(), "Hello,");
430        assert!(s.is_inline());
431        assert!(s.capacity() == 16);
432
433        s.push(' ');
434        s.push_str("World");
435        assert_eq!(s.len(), 12);
436        assert_eq!(&*s, "Hello, World");
437        assert!(s.is_inline());
438        assert!(s.capacity() == 16);
439    }
440
441    #[test]
442    fn test_basic_spill_to_heap() {
443        let mut s: SmallString<16> = SmallString::new();
444        assert!(s.is_inline());
445        assert!(s.is_empty());
446        assert!(s.capacity() == 16);
447
448        s.push_str("Hello, ");
449        assert_eq!(s.len(), 7);
450        assert_eq!(s.as_str(), "Hello, ");
451        assert!(s.is_inline());
452        assert!(s.capacity() == 16);
453
454        s.push_str(&"a".repeat(30));
455        assert_eq!(s.len(), 37);
456        assert_eq!(&s, &format!("Hello, {}", "a".repeat(30)));
457        assert!(!s.is_inline());
458        assert!(s.capacity() >= 37);
459    }
460
461    // -----------------------------------------------------------------------
462    // Construction methods
463    // -----------------------------------------------------------------------
464
465    #[test]
466    fn test_new_is_empty_and_default() {
467        let s: SmallString<8> = SmallString::new();
468        assert!(s.is_empty());
469        assert!(s.is_inline());
470        let s2: SmallString<8> = Default::default();
471        assert!(s2.is_empty());
472        assert!(s2.is_inline());
473    }
474
475    #[test]
476    fn test_from_str_inline() {
477        let s: SmallString<32> = SmallString::from_str("hello");
478        assert!(s.is_inline());
479        assert_eq!(s.as_str(), "hello");
480    }
481
482    #[test]
483    fn test_from_str_spill() {
484        let s: SmallString<4> = SmallString::from_str("hello world");
485        assert!(!s.is_inline());
486        assert_eq!(s.as_str(), "hello world");
487    }
488
489    #[test]
490    fn test_from_str_exact_capacity() {
491        let s: SmallString<5> = SmallString::from_str("hello");
492        assert!(s.is_inline());
493        assert_eq!(s.as_str(), "hello");
494    }
495
496    #[test]
497    fn test_from_string_heap_preserved() {
498        let heap = String::from("hello world this is a long string");
499        let s: SmallString<4> = SmallString::from_string(heap.clone());
500        assert!(!s.is_inline());
501        assert_eq!(s.as_str(), heap);
502    }
503
504    #[test]
505    fn test_from_string_small() {
506        let heap = String::from("hi");
507        let s: SmallString<32> = SmallString::from_string(heap);
508        // from_string always stores in Heap variant
509        assert!(!s.is_inline());
510        assert_eq!(s.as_str(), "hi");
511    }
512
513    #[test]
514    fn test_from_utf8_valid() {
515        let s: SmallString<16> = SmallString::from_utf8(b"hello".to_vec()).unwrap();
516        assert!(s.is_inline() == false);
517        assert_eq!(s.as_str(), "hello");
518    }
519
520    #[test]
521    fn test_from_utf8_invalid() {
522        let result: Result<SmallString<16>, _> = SmallString::from_utf8(vec![0xFF, 0xFE]);
523        assert!(result.is_err());
524    }
525
526    #[test]
527    fn test_from_utf8_slice_valid() {
528        let s: SmallString<16> = SmallString::from_utf8_slice(b"hello").unwrap();
529        assert!(s.is_inline() == true);
530        assert_eq!(s.as_str(), "hello");
531    }
532
533    #[test]
534    fn test_from_utf8_slice_invalid() {
535        let result: Result<SmallString<16>, _> = SmallString::from_utf8_slice(&[0xFF, 0xFE]);
536        assert!(result.is_err());
537    }
538
539    #[test]
540    fn test_from_utf8_lossy_valid() {
541        let s: SmallString<16> = SmallString::from_utf8_lossy(b"hello");
542        assert!(s.is_inline());
543        assert_eq!(s.as_str(), "hello");
544    }
545
546    #[test]
547    fn test_from_utf8_lossy_invalid() {
548        let s: SmallString<16> = SmallString::from_utf8_lossy(&[0xFF, 0xFE]);
549        // Replacement character(s)
550        assert_eq!(s.as_str(), "\u{FFFD}\u{FFFD}");
551    }
552
553    #[test]
554    fn test_from_utf8_lossy_spill() {
555        let bytes = b"hello world this is long";
556        let s: SmallString<4> = SmallString::from_utf8_lossy(bytes);
557        assert!(!s.is_inline());
558        assert_eq!(s.as_str(), "hello world this is long");
559    }
560
561    // -----------------------------------------------------------------------
562    // From trait impls
563    // -----------------------------------------------------------------------
564
565    #[test]
566    fn test_from_str_trait_inline() {
567        let s: SmallString<16> = SmallString::from("hi");
568        assert!(s.is_inline());
569    }
570
571    #[test]
572    fn test_from_str_trait_spill() {
573        let s: SmallString<4> = SmallString::from("long string");
574        assert!(!s.is_inline());
575    }
576
577    #[test]
578    fn test_from_string_trait() {
579        let s: SmallString<32> = SmallString::from(String::from("hi"));
580        assert!(!s.is_inline());
581        assert_eq!(s.as_str(), "hi");
582    }
583
584    // -----------------------------------------------------------------------
585    // Inspection methods
586    // -----------------------------------------------------------------------
587
588    #[test]
589    fn test_capacity_inline() {
590        let s: SmallString<64> = SmallString::from_str("hello");
591        assert_eq!(s.capacity(), 64);
592    }
593
594    #[test]
595    fn test_capacity_heap() {
596        let s: SmallString<4> = SmallString::from_str("hello world, this is a test!");
597        assert!(!s.is_inline());
598        assert!(s.capacity() >= s.len());
599    }
600
601    #[test]
602    fn test_len() {
603        let s: SmallString<32> = SmallString::from_str("héllo");
604        assert_eq!(s.len(), 6);
605    }
606
607    // -----------------------------------------------------------------------
608    // Mutation methods
609    // -----------------------------------------------------------------------
610
611    #[test]
612    fn test_clear_inline() {
613        let mut s: SmallString<16> = SmallString::from_str("hello");
614        assert!(s.is_inline());
615        s.clear();
616        assert!(s.is_empty());
617        assert!(s.is_inline());
618        assert_eq!(s.capacity(), 16);
619    }
620
621    #[test]
622    fn test_clear_heap() {
623        let mut s: SmallString<4> = SmallString::from_str("long string content");
624        assert!(!s.is_inline());
625        s.clear();
626        assert!(s.is_empty());
627        assert!(!s.is_inline());
628    }
629
630    #[test]
631    fn test_clear_and_reuse_inline() {
632        let mut s: SmallString<16> = SmallString::from_str("hello");
633        s.clear();
634        s.push_str("world");
635        assert!(s.is_inline());
636        assert_eq!(s.as_str(), "world");
637    }
638
639    #[test]
640    fn test_clear_and_reuse_heap() {
641        let mut s: SmallString<4> = SmallString::from_str("long string content");
642        s.clear();
643        s.push_str("abc");
644        assert!(!s.is_inline());
645        assert_eq!(s.as_str(), "abc");
646    }
647
648    #[test]
649    fn test_push_char_inline() {
650        let mut s: SmallString<16> = SmallString::new();
651        s.push('a');
652        s.push('b');
653        s.push('c');
654        assert!(s.is_inline());
655        assert_eq!(s.as_str(), "abc");
656    }
657
658    #[test]
659    fn test_push_char_spill() {
660        let mut s: SmallString<4> = SmallString::from_str("abc");
661        assert!(s.is_inline());
662        s.push('d');
663        // exactly at capacity, still inline
664        assert!(s.is_inline());
665        assert_eq!(s.as_str(), "abcd");
666
667        s.push('e');
668        assert!(!s.is_inline());
669        assert_eq!(s.as_str(), "abcde");
670    }
671
672    #[test]
673    fn test_push_multibyte_char_stays_inline() {
674        let mut s: SmallString<8> = SmallString::new();
675        s.push('€');
676        assert!(s.is_inline());
677        assert_eq!(s.as_str(), "€");
678    }
679
680    #[test]
681    fn test_push_multibyte_char_spill() {
682        let mut s: SmallString<4> = SmallString::from_str("a");
683        // '🦀' is 4 bytes, 'a' is 1 byte, so this spills
684        s.push('🦀');
685        assert!(!s.is_inline());
686        assert_eq!(s.as_str(), "a🦀");
687    }
688
689    #[test]
690    fn test_push_str_spill_with_char() {
691        let mut s: SmallString<4> = SmallString::from_str("a");
692        s.push_str("bcd");
693        assert!(s.is_inline());
694        assert_eq!(s.as_str(), "abcd");
695
696        s.push_str("e");
697        assert!(!s.is_inline());
698        assert_eq!(s.as_str(), "abcde");
699    }
700
701    #[test]
702    fn test_push_str_exact_boundary() {
703        let mut s: SmallString<5> = SmallString::from_str("hello");
704        assert!(s.is_inline());
705        // push empty str on full buffer
706        s.push_str("");
707        assert!(s.is_inline());
708        assert_eq!(s.as_str(), "hello");
709    }
710
711    #[test]
712    fn test_pop_inline() {
713        let mut s: SmallString<16> = SmallString::from_str("hello");
714        assert_eq!(s.pop(), Some('o'));
715        assert_eq!(s.pop(), Some('l'));
716        assert_eq!(s.as_str(), "hel");
717    }
718
719    #[test]
720    fn test_pop_heap() {
721        let mut s: SmallString<4> = SmallString::from_str("hello!!!");
722        assert!(!s.is_inline());
723        assert_eq!(s.pop(), Some('!'));
724        assert_eq!(s.pop(), Some('!'));
725        assert_eq!(s.as_str(), "hello!");
726    }
727
728    #[test]
729    fn test_pop_empty() {
730        let mut s: SmallString<16> = SmallString::new();
731        assert_eq!(s.pop(), None);
732    }
733
734    #[test]
735    fn test_pop_empty_after_clear() {
736        let mut s: SmallString<16> = SmallString::from_str("a");
737        s.clear();
738        assert_eq!(s.pop(), None);
739    }
740
741    #[test]
742    fn test_pop_multibyte() {
743        let mut s: SmallString<16> = SmallString::from_str("a🦀b");
744        assert_eq!(s.pop(), Some('b'));
745        assert_eq!(s.pop(), Some('🦀'));
746        assert_eq!(s.as_str(), "a");
747    }
748
749    #[test]
750    fn test_truncate_inline() {
751        let mut s: SmallString<16> = SmallString::from_str("hello world");
752        s.truncate(5);
753        assert_eq!(s.as_str(), "hello");
754        assert!(s.is_inline());
755    }
756
757    #[test]
758    fn test_truncate_heap() {
759        let mut s: SmallString<4> = SmallString::from_str("hello world");
760        assert!(!s.is_inline());
761        s.truncate(5);
762        assert_eq!(s.as_str(), "hello");
763        assert!(!s.is_inline());
764    }
765
766    #[test]
767    fn test_truncate_zero() {
768        let mut s: SmallString<16> = SmallString::from_str("hello");
769        s.truncate(0);
770        assert!(s.is_empty());
771    }
772
773    #[test]
774    fn test_truncate_past_len() {
775        let mut s: SmallString<16> = SmallString::from_str("hi");
776        s.truncate(100);
777        assert_eq!(s.as_str(), "hi");
778    }
779
780    #[test]
781    fn test_reserve_no_spill() {
782        let mut s: SmallString<16> = SmallString::from_str("hi");
783        s.reserve(4);
784        assert!(s.is_inline());
785    }
786
787    #[test]
788    fn test_reserve_triggers_spill() {
789        let mut s: SmallString<8> = SmallString::from_str("hi");
790        s.reserve(10);
791        assert!(!s.is_inline());
792        assert_eq!(s.as_str(), "hi");
793    }
794
795    #[test]
796    fn test_reserve_on_heap() {
797        let mut s: SmallString<4> = SmallString::from_str("hello world");
798        assert!(!s.is_inline());
799        let cap_before = s.capacity();
800        s.reserve(50);
801        assert!(s.capacity() >= cap_before + 50);
802        assert_eq!(s.as_str(), "hello world");
803    }
804
805    // -----------------------------------------------------------------------
806    // Access methods
807    // -----------------------------------------------------------------------
808
809    #[test]
810    fn test_as_str() {
811        let s: SmallString<16> = SmallString::from_str("hello");
812        assert_eq!(s.as_str(), "hello");
813    }
814
815    #[test]
816    fn test_as_mut_str() {
817        let mut s: SmallString<16> = SmallString::from_str("hello");
818        let ms = s.as_mut_str();
819        ms.make_ascii_uppercase();
820        assert_eq!(s.as_str(), "HELLO");
821    }
822
823    #[test]
824    fn test_as_bytes() {
825        let s: SmallString<16> = SmallString::from_str("hello");
826        assert_eq!(s.as_bytes(), b"hello");
827    }
828
829    #[test]
830    fn test_as_bytes_mut() {
831        let mut s: SmallString<16> = SmallString::from_str("hello");
832        let bytes = unsafe { s.as_bytes_mut() };
833        bytes[0] = b'H';
834        assert_eq!(s.as_str(), "Hello");
835    }
836
837    #[test]
838    fn test_as_bytes_heap() {
839        let s: SmallString<4> = SmallString::from_str("hello world");
840        assert!(!s.is_inline());
841        assert_eq!(s.as_bytes(), b"hello world");
842    }
843
844    // -----------------------------------------------------------------------
845    // Deref and DerefMut
846    // -----------------------------------------------------------------------
847
848    #[test]
849    fn test_deref_inline() {
850        let s: SmallString<16> = SmallString::from_str("hello");
851        let r: &str = &*s;
852        assert_eq!(r, "hello");
853    }
854
855    #[test]
856    fn test_deref_heap() {
857        let s: SmallString<4> = SmallString::from_str("long string");
858        let r: &str = &*s;
859        assert_eq!(r, "long string");
860    }
861
862    #[test]
863    fn test_deref_mut() {
864        let mut s: SmallString<16> = SmallString::from_str("hello");
865        let r: &mut str = &mut *s;
866        r.make_ascii_uppercase();
867        assert_eq!(s.as_str(), "HELLO");
868    }
869
870    // -----------------------------------------------------------------------
871    // Display, Debug, fmt::Write
872    // -----------------------------------------------------------------------
873
874    #[test]
875    fn test_display() {
876        let s: SmallString<16> = SmallString::from_str("hello");
877        assert_eq!(format!("{}", s), "hello");
878    }
879
880    #[test]
881    fn test_display_heap() {
882        let s: SmallString<4> = SmallString::from_str("hello world");
883        assert_eq!(format!("{}", s), "hello world");
884    }
885
886    #[test]
887    fn test_debug_inline() {
888        let s: SmallString<16> = SmallString::from_str("hello");
889        assert_eq!(format!("{:?}", s), "Inline(\"hello\")");
890    }
891
892    #[test]
893    fn test_debug_heap() {
894        let s: SmallString<4> = SmallString::from_str("hello world");
895        assert_eq!(format!("{:?}", s), "Heap(\"hello world\")");
896    }
897
898    #[test]
899    fn test_fmt_write() {
900        let mut s: SmallString<16> = SmallString::new();
901        write!(&mut s, "hello {} {}", "world", 42).unwrap();
902        assert_eq!(s.as_str(), "hello world 42");
903        assert!(s.is_inline());
904    }
905
906    #[test]
907    fn test_fmt_write_spill() {
908        let mut s: SmallString<4> = SmallString::new();
909        write!(&mut s, "hello world").unwrap();
910        assert!(!s.is_inline());
911        assert_eq!(s.as_str(), "hello world");
912    }
913
914    // -----------------------------------------------------------------------
915    // Equality
916    // -----------------------------------------------------------------------
917
918    #[test]
919    fn test_eq_inline_inline() {
920        let a: SmallString<16> = SmallString::from_str("hello");
921        let b: SmallString<16> = SmallString::from_str("hello");
922        assert_eq!(a, b);
923    }
924
925    #[test]
926    fn test_eq_inline_heap() {
927        let a: SmallString<4> = SmallString::from_str("hello");
928        let b: SmallString<16> = SmallString::from_str("hello");
929        assert!(!a.is_inline());
930        assert!(b.is_inline());
931        assert_eq!(a, b);
932    }
933
934    #[test]
935    fn test_eq_cross_n() {
936        let a: SmallString<8> = SmallString::from_str("test");
937        let b: SmallString<32> = SmallString::from_str("test");
938        assert_eq!(a, b);
939    }
940
941    #[test]
942    fn test_eq_inequality() {
943        let a: SmallString<16> = SmallString::from_str("abc");
944        let b: SmallString<16> = SmallString::from_str("xyz");
945        assert_ne!(a, b);
946    }
947
948    #[test]
949    fn test_partial_eq_str() {
950        let s: SmallString<16> = SmallString::from_str("hello");
951        assert_eq!(s, *"hello");
952    }
953
954    #[test]
955    fn test_partial_eq_ref_str() {
956        let s: SmallString<16> = SmallString::from_str("hello");
957        let r: &str = "hello";
958        assert_eq!(s, r);
959    }
960
961    #[test]
962    fn test_partial_eq_str_ref_left() {
963        let s: SmallString<16> = SmallString::from_str("hello");
964        assert_eq!("hello", s);
965    }
966
967    #[test]
968    fn test_partial_eq_string() {
969        let s: SmallString<16> = SmallString::from_str("hello");
970        let heap = String::from("hello");
971        assert_eq!(s, heap);
972    }
973
974    // -----------------------------------------------------------------------
975    // Ordering
976    // -----------------------------------------------------------------------
977
978    #[test]
979    fn test_ord() {
980        let a: SmallString<16> = SmallString::from_str("abc");
981        let b: SmallString<16> = SmallString::from_str("xyz");
982        assert!(a < b);
983        assert!(b > a);
984    }
985
986    #[test]
987    fn test_ord_cross_n() {
988        let a: SmallString<4> = SmallString::from_str("abc");
989        let b: SmallString<32> = SmallString::from_str("xyz");
990        assert!(a < b);
991    }
992
993    #[test]
994    fn test_ord_equal() {
995        let a: SmallString<16> = SmallString::from_str("same");
996        let b: SmallString<16> = SmallString::from_str("same");
997        assert_eq!(a.cmp(&b), core::cmp::Ordering::Equal);
998    }
999
1000    #[test]
1001    fn test_partial_ord_cross_m() {
1002        let a: SmallString<8> = SmallString::from_str("a");
1003        let b: SmallString<32> = SmallString::from_str("b");
1004        assert!(a < b);
1005    }
1006
1007    // -----------------------------------------------------------------------
1008    // Hash
1009    // -----------------------------------------------------------------------
1010
1011    #[test]
1012    fn test_hash_inline() {
1013        use std::{
1014            collections::hash_map::DefaultHasher,
1015            hash::{Hash, Hasher},
1016        };
1017
1018        let s: SmallString<16> = SmallString::from_str("hello");
1019        let mut hasher = DefaultHasher::new();
1020        s.hash(&mut hasher);
1021        let h1 = hasher.finish();
1022
1023        let mut hasher = DefaultHasher::new();
1024        "hello".hash(&mut hasher);
1025        let h2 = hasher.finish();
1026        assert_eq!(h1, h2);
1027    }
1028
1029    #[test]
1030    fn test_hash_inline_and_heap_same() {
1031        use std::{
1032            collections::hash_map::DefaultHasher,
1033            hash::{Hash, Hasher},
1034        };
1035
1036        let inline: SmallString<32> = SmallString::from_str("hello world");
1037        let heap: SmallString<4> = SmallString::from_str("hello world");
1038
1039        let mut h1 = DefaultHasher::new();
1040        inline.hash(&mut h1);
1041        let mut h2 = DefaultHasher::new();
1042        heap.hash(&mut h2);
1043        assert_eq!(h1.finish(), h2.finish());
1044    }
1045
1046    #[test]
1047    fn test_hash_in_hashmap() {
1048        use std::collections::HashSet;
1049
1050        let a: SmallString<16> = SmallString::from_str("key1");
1051        let b: SmallString<16> = SmallString::from_str("key1");
1052
1053        let mut set = HashSet::new();
1054        set.insert(a);
1055        assert!(set.contains(&b));
1056    }
1057
1058    // -----------------------------------------------------------------------
1059    // Borrow / BorrowMut / AsRef
1060    // -----------------------------------------------------------------------
1061
1062    #[test]
1063    fn test_borrow_str() {
1064        use std::borrow::Borrow;
1065        let s: SmallString<16> = SmallString::from_str("hello");
1066        let r: &str = s.borrow();
1067        assert_eq!(r, "hello");
1068    }
1069
1070    #[test]
1071    fn test_borrow_mut_str() {
1072        use std::borrow::BorrowMut;
1073        let mut s: SmallString<16> = SmallString::from_str("hello");
1074        let r: &mut str = s.borrow_mut();
1075        r.make_ascii_uppercase();
1076        assert_eq!(s.as_str(), "HELLO");
1077    }
1078
1079    #[test]
1080    fn test_as_ref_str() {
1081        let s: SmallString<16> = SmallString::from_str("hello");
1082        let r: &str = s.as_ref();
1083        assert_eq!(r, "hello");
1084    }
1085
1086    #[test]
1087    fn test_as_ref_bytes() {
1088        let s: SmallString<16> = SmallString::from_str("hello");
1089        let r: &[u8] = s.as_ref();
1090        assert_eq!(r, b"hello");
1091    }
1092
1093    // -----------------------------------------------------------------------
1094    // Clone
1095    // -----------------------------------------------------------------------
1096
1097    #[test]
1098    fn test_clone_inline() {
1099        let s: SmallString<16> = SmallString::from_str("hello");
1100        let c = s.clone();
1101        assert!(c.is_inline());
1102        assert_eq!(c, s);
1103    }
1104
1105    #[test]
1106    fn test_clone_heap() {
1107        let s: SmallString<4> = SmallString::from_str("hello world");
1108        assert!(!s.is_inline());
1109        let c = s.clone();
1110        assert!(!c.is_inline());
1111        assert_eq!(c, s);
1112    }
1113
1114    #[test]
1115    fn test_clone_independent() {
1116        let s: SmallString<16> = SmallString::from_str("hello");
1117        let mut c = s.clone();
1118        c.push_str(" world");
1119        assert_eq!(s.as_str(), "hello");
1120        assert_eq!(c.as_str(), "hello world");
1121    }
1122
1123    // -----------------------------------------------------------------------
1124    // FromIterator
1125    // -----------------------------------------------------------------------
1126
1127    #[test]
1128    fn test_from_iter_chars_empty() {
1129        let s: SmallString<16> = SmallString::from_iter("".chars());
1130        assert!(s.is_empty());
1131    }
1132
1133    #[test]
1134    fn test_from_iter_chars_inline() {
1135        let s: SmallString<16> = SmallString::from_iter("hello".chars());
1136        assert!(s.is_inline());
1137        assert_eq!(s.as_str(), "hello");
1138    }
1139
1140    #[test]
1141    fn test_from_iter_chars_spill() {
1142        let s: SmallString<4> = SmallString::from_iter("hello world".chars());
1143        assert!(!s.is_inline());
1144        assert_eq!(s.as_str(), "hello world");
1145    }
1146
1147    #[test]
1148    fn test_from_iter_strs_empty() {
1149        let s: SmallString<16> = [""].iter().copied().collect::<SmallString<16>>();
1150        assert!(s.is_empty());
1151    }
1152
1153    #[test]
1154    fn test_from_iter_strs_inline() {
1155        let parts = ["hello", " ", "world"];
1156        let s: SmallString<32> = parts.iter().copied().collect();
1157        assert!(s.is_inline());
1158        assert_eq!(s.as_str(), "hello world");
1159    }
1160
1161    #[test]
1162    fn test_from_iter_strs_spill() {
1163        let parts = ["hello", " ", "world", " ", "this is long"];
1164        let s: SmallString<4> = parts.iter().copied().collect();
1165        assert!(!s.is_inline());
1166        assert_eq!(s.as_str(), "hello world this is long");
1167    }
1168
1169    // -----------------------------------------------------------------------
1170    // Extend
1171    // -----------------------------------------------------------------------
1172
1173    #[test]
1174    fn test_extend_chars_inline() {
1175        let mut s: SmallString<16> = SmallString::from_str("he");
1176        s.extend("llo".chars());
1177        assert!(s.is_inline());
1178        assert_eq!(s.as_str(), "hello");
1179    }
1180
1181    #[test]
1182    fn test_extend_chars_spill() {
1183        let mut s: SmallString<4> = SmallString::from_str("a");
1184        s.extend("bcdef".chars());
1185        assert!(!s.is_inline());
1186        assert_eq!(s.as_str(), "abcdef");
1187    }
1188
1189    #[test]
1190    fn test_extend_strs_inline() {
1191        let mut s: SmallString<16> = SmallString::from_str("hello");
1192        s.extend([" ", "world"]);
1193        assert!(s.is_inline());
1194        assert_eq!(s.as_str(), "hello world");
1195    }
1196
1197    #[test]
1198    fn test_extend_strs_spill() {
1199        let mut s: SmallString<4> = SmallString::from_str("a");
1200        s.extend(["bc", "def"]);
1201        assert!(!s.is_inline());
1202        assert_eq!(s.as_str(), "abcdef");
1203    }
1204
1205    // -----------------------------------------------------------------------
1206    // Edge cases
1207    // -----------------------------------------------------------------------
1208
1209    #[test]
1210    fn test_zero_capacity() {
1211        let mut s: SmallString<0> = SmallString::new();
1212        assert!(s.is_inline());
1213        assert!(s.is_empty());
1214        assert_eq!(s.capacity(), 0);
1215
1216        // Any push should spill immediately
1217        s.push_str("x");
1218        assert!(!s.is_inline());
1219        assert_eq!(s.as_str(), "x");
1220
1221        // Push char on zero capacity
1222        let mut s2: SmallString<0> = SmallString::new();
1223        s2.push('a');
1224        assert!(!s2.is_inline());
1225        assert_eq!(s2.as_str(), "a");
1226    }
1227
1228    #[test]
1229    fn test_zero_capacity_from_str() {
1230        let s: SmallString<0> = SmallString::from_str("");
1231        assert!(s.is_inline());
1232        assert!(s.is_empty());
1233
1234        let s2: SmallString<0> = SmallString::from_str("x");
1235        assert!(!s2.is_inline());
1236        assert_eq!(s2.as_str(), "x");
1237    }
1238
1239    #[test]
1240    fn test_multibyte_char_boundary() {
1241        let mut s: SmallString<8> = SmallString::new();
1242        s.push_str("a🦀b"); // 'a'=1, '🦀'=4, 'b'=1 => total 6
1243        assert!(s.is_inline());
1244        assert_eq!(s.len(), 6);
1245        assert_eq!(s.as_str(), "a🦀b");
1246
1247        // Ensure indexing / slicing works correctly
1248        let chars: Vec<char> = s.chars().collect();
1249        assert_eq!(chars, vec!['a', '🦀', 'b']);
1250    }
1251
1252    #[test]
1253    fn test_deref_methods_available() {
1254        let s: SmallString<16> = SmallString::from_str("hello world");
1255        // Methods from str through Deref
1256        assert!(s.contains("world"));
1257        assert!(s.starts_with("hello"));
1258        assert_eq!(s.find('w'), Some(6));
1259        let words: Vec<&str> = s.split(' ').collect();
1260        assert_eq!(words, vec!["hello", "world"]);
1261    }
1262
1263    #[test]
1264    fn test_roundtrip_format() {
1265        let s: SmallString<16> = SmallString::from_str("test");
1266        let formatted = format!("{}", s);
1267        let back: SmallString<16> = SmallString::from_str(&formatted);
1268        assert_eq!(s, back);
1269    }
1270
1271    #[test]
1272    fn test_empty_str_operations() {
1273        let mut s: SmallString<16> = SmallString::from_str("");
1274        assert!(s.is_empty());
1275        s.push_str("");
1276        assert!(s.is_empty());
1277        s.push_str("a");
1278        assert_eq!(s.len(), 1);
1279    }
1280
1281    #[test]
1282    fn test_from_small_string_inline_to_inline() {
1283        let source: SmallString<16> = SmallString::from_str("hello");
1284        assert!(source.is_inline());
1285
1286        let dest: SmallString<32> = SmallString::from_small_string(source);
1287        assert!(dest.is_inline());
1288        assert_eq!(dest.as_str(), "hello");
1289    }
1290
1291    #[test]
1292    fn test_from_small_string_inline_to_spill() {
1293        let source: SmallString<16> = SmallString::from_str("hello world");
1294        assert!(source.is_inline());
1295
1296        let dest: SmallString<4> = SmallString::from_small_string(source);
1297        assert!(!dest.is_inline());
1298        assert_eq!(dest.as_str(), "hello world");
1299    }
1300
1301    #[test]
1302    fn test_from_small_string_heap_to_inline() {
1303        // Force heap by exceeding inline capacity
1304        let source: SmallString<4> = SmallString::from_str("hello world this is long");
1305        assert!(!source.is_inline());
1306
1307        // Target has enough capacity to fit inline
1308        let dest: SmallString<64> = SmallString::from_small_string(source);
1309        assert!(dest.is_inline());
1310        assert_eq!(dest.as_str(), "hello world this is long");
1311    }
1312
1313    #[test]
1314    fn test_from_small_string_heap_to_heap() {
1315        // Force heap by exceeding inline capacity
1316        let source: SmallString<16> = SmallString::from_str("hello world this is even longer string content");
1317        assert!(!source.is_inline());
1318
1319        // Target too small to fit inline
1320        let dest: SmallString<4> = SmallString::from_small_string(source);
1321        assert!(!dest.is_inline());
1322        assert_eq!(dest.as_str(), "hello world this is even longer string content");
1323    }
1324
1325    #[test]
1326    fn test_from_small_string_empty() {
1327        let source: SmallString<16> = SmallString::new();
1328        assert!(source.is_inline());
1329        assert!(source.is_empty());
1330
1331        let dest: SmallString<8> = SmallString::from_small_string(source);
1332        assert!(dest.is_inline());
1333        assert!(dest.is_empty());
1334    }
1335
1336    #[test]
1337    fn test_from_small_string_exact_capacity() {
1338        let source: SmallString<16> = SmallString::from_str("abcd");
1339        assert!(source.is_inline());
1340
1341        let dest: SmallString<4> = SmallString::from_small_string(source);
1342        assert!(dest.is_inline());
1343        assert_eq!(dest.as_str(), "abcd");
1344    }
1345
1346    #[cfg(feature = "serde")]
1347    #[test]
1348    fn test_serde_roundtrip_inline() {
1349        let s: SmallString<16> = SmallString::from_str("hello");
1350        let json = serde_json::to_string(&s).unwrap();
1351        let deserialized: SmallString<16> = serde_json::from_str(&json).unwrap();
1352        assert_eq!(s, deserialized);
1353        assert!(deserialized.is_inline());
1354    }
1355
1356    #[cfg(feature = "serde")]
1357    #[test]
1358    fn test_serde_roundtrip_heap() {
1359        let s: SmallString<4> = SmallString::from_str("hello world long");
1360        let json = serde_json::to_string(&s).unwrap();
1361        let deserialized: SmallString<4> = serde_json::from_str(&json).unwrap();
1362        assert_eq!(s, deserialized);
1363        // Deserialized smallstring will fit inline on the target size
1364    }
1365}