Skip to main content

sinstr/
non_empty.rs

1use core::{
2    alloc::Layout,
3    borrow::{Borrow, BorrowMut},
4    fmt::{Debug, Display},
5    hash::Hash,
6    hint::assert_unchecked,
7    mem::{MaybeUninit, transmute},
8    num::{NonZeroU8, NonZeroUsize},
9    ops::{Deref, DerefMut},
10    ptr::{self, NonNull},
11};
12
13use alloc::alloc::{alloc, dealloc, handle_alloc_error, realloc};
14
15use crate::{
16    discriminant::{DiscriminantValues, NICHE_MAX_INT},
17    likely, unlikely,
18};
19
20#[repr(C)]
21struct HeapRepr(NonZeroUsize);
22
23impl HeapRepr {
24    /// Checks if the provided value is a valid length for a heap string.
25    ///
26    /// If the provided value is a valid slice length (`isize::MAX`) and greater than
27    /// [`NICHE_MAX_INT`] returns `true`.
28    #[inline(always)]
29    const fn is_valid_len(len: usize) -> bool {
30        len < isize::MAX as usize && NICHE_MAX_INT < len
31    }
32
33    #[inline(always)]
34    pub const fn as_ptr(&self) -> NonNull<NonZeroUsize> {
35        // SAFETY: This can already be done safely via `NonNull::with_exposed_provenance` but it isnt
36        // const.
37        //
38        // We already satisfy requirements of NonNull since we are using NonZeroUsize
39        //
40        // https://github.com/rust-lang/rust/issues/154215
41        unsafe {
42            NonNull::new_unchecked(
43                core::ptr::with_exposed_provenance::<NonZeroUsize>(self.0.get()) as _,
44            )
45        }
46    }
47
48    #[inline(always)]
49    #[allow(unused)]
50    pub const fn as_str_ptr(&mut self) -> NonNull<u8> {
51        // SAFETY: They are part of the same allocation.
52        // It cannot overflow isize.
53        unsafe { self.as_ptr().add(1).cast() }
54    }
55
56    #[inline(always)]
57    pub const fn as_ptr_mut(&mut self) -> NonNull<NonZeroUsize> {
58        unsafe { NonNull::new_unchecked(core::ptr::with_exposed_provenance_mut(self.0.get())) }
59    }
60
61    #[inline(always)]
62    pub const fn as_str_ptr_mut(&mut self) -> NonNull<u8> {
63        // SAFETY: They are part of the same allocation.
64        // It cannot overflow isize.
65        unsafe { self.as_ptr_mut().add(1).cast() }
66    }
67
68    /// Returns the length of the stored string.
69    ///
70    /// Returns a [`NonZeroUsize`] as [`HeapRepr`] is always greater than [`NICHE_MAX_INT`].
71    #[allow(clippy::len_without_is_empty)]
72    #[inline(always)]
73    pub const fn len(&self) -> NonZeroUsize {
74        // SAFETY: pointer is always non null and properly aligned with enough provenance to read a usize
75        unsafe { self.as_ptr().read() }
76    }
77
78    #[inline(always)]
79    pub const fn set_len(&mut self, len: NonZeroUsize) {
80        // SAFETY: pointer is always non null and properly aligned with enough provenance to read a usize
81        unsafe { self.as_ptr().write(len) }
82    }
83
84    /// Returns the string as a slice of bytes.
85    #[inline(always)]
86    const fn as_bytes(&self) -> &[u8] {
87        let ptr = self.as_ptr();
88        let len = self.len();
89        // SAFETY: pointer is always non null and properly aligned with enough provenance to read a usize + len bytes
90        unsafe { NonNull::slice_from_raw_parts(ptr.add(1).cast::<u8>(), len.get()).as_ref() }
91    }
92
93    /// Returns the string as a mutable slice of bytes.
94    #[inline(always)]
95    const unsafe fn as_bytes_mut(&mut self) -> &mut [u8] {
96        let ptr = self.as_ptr_mut();
97        let len = self.len();
98        unsafe { NonNull::slice_from_raw_parts(ptr.add(1).cast::<u8>(), len.get()).as_mut() }
99    }
100
101    /// Returns the string as a `&str`.
102    #[inline(always)]
103    const fn as_str(&self) -> &str {
104        // SAFETY: The bytes were copied from a valid &str during construction
105        // and haven't been mutated, so they remain valid UTF-8.
106        unsafe { str::from_utf8_unchecked(self.as_bytes()) }
107    }
108
109    /// Returns the string as a `&mut str`.
110    #[inline(always)]
111    const fn as_str_mut(&mut self) -> &mut str {
112        // SAFETY: The bytes were copied from a valid &str during construction.
113        // The caller of as_str_mut() must preserve UTF-8 validity.
114        unsafe { str::from_utf8_unchecked_mut(self.as_bytes_mut()) }
115    }
116
117    /// Returns the total capacity of the allocation.
118    #[inline(always)]
119    const fn capacity(&self) -> NonZeroUsize {
120        // SAFETY: The size and capacity was already validated during initialization.
121        unsafe {
122            NonZeroUsize::new_unchecked(next_step(
123                self.len().get().unchecked_add(size_of::<usize>()),
124            ))
125        }
126    }
127
128    /// Returns the total capacity of the allocation.
129    #[inline(always)]
130    #[allow(unused)]
131    const fn str_capacity(&self) -> NonZeroUsize {
132        // SAFETY: The size and capacity was already validated during initialization.
133        unsafe { NonZeroUsize::new_unchecked(next_step(self.len().get())) }
134    }
135}
136
137#[repr(C)]
138struct InlinedRepr {
139    _align: [usize; 0],
140    #[cfg(target_endian = "big")]
141    data: [MaybeUninit<u8>; size_of::<NonZeroUsize>() - 1],
142    len: NonZeroU8,
143    #[cfg(target_endian = "little")]
144    data: [MaybeUninit<u8>; size_of::<NonZeroUsize>() - 1],
145}
146
147impl InlinedRepr {
148    /// Returns the string as a slice of bytes.
149    #[inline(always)]
150    const fn as_bytes(&self) -> &[u8] {
151        // SAFETY: len field always contains the number of initialized bytes
152        unsafe {
153            core::slice::from_raw_parts(&raw const self.data as *const u8, self.len.get() as usize)
154        }
155    }
156
157    /// Returns the string as a mutable slice of bytes.
158    #[inline(always)]
159    const unsafe fn as_bytes_mut(&mut self) -> &mut [u8] {
160        // SAFETY: len field always contains the number of initialized bytes
161        unsafe {
162            core::slice::from_raw_parts_mut(&raw mut self.data as *mut u8, self.len.get() as usize)
163        }
164    }
165
166    /// Returns the string as a `&str`.
167    #[inline(always)]
168    const fn as_str(&self) -> &str {
169        // SAFETY: The bytes were copied from a valid &str during construction
170        // and haven't been mutated, so they remain valid UTF-8.
171        unsafe { str::from_utf8_unchecked(self.as_bytes()) }
172    }
173
174    /// Returns the string as a `&mut str`.
175    #[inline(always)]
176    const fn as_str_mut(&mut self) -> &mut str {
177        // SAFETY: The bytes were copied from a valid &str during construction.
178        // The caller of as_str_mut() must preserve UTF-8 validity.
179        unsafe { str::from_utf8_unchecked_mut(self.as_bytes_mut()) }
180    }
181}
182
183#[repr(C)]
184pub struct NonEmptySinStr {
185    _align: [usize; 0], // Zero-sized, forces usize alignment
186    #[cfg(target_endian = "big")]
187    /// Depending on `len` this is the inline strings data portion or a pointer with out its least significant byte.
188    data_or_partial_ptr: [MaybeUninit<u8>; size_of::<NonZeroUsize>() - 1],
189    /// An enum with all of the possible values the least significant byte can have.
190    ///
191    /// Depending on the value; it contains the length for an inlined string or the least
192    /// significant byte for a pointer to an allocation.
193    disc: DiscriminantValues,
194    #[cfg(target_endian = "little")]
195    /// Depending on `len` this is the inline strings data portion or a pointer with out its least significant byte.
196    data_or_partial_ptr: [MaybeUninit<u8>; size_of::<NonZeroUsize>() - 1],
197}
198
199impl Clone for NonEmptySinStr {
200    fn clone(&self) -> Self {
201        if self.is_inlined() {
202            // SAFETY: An inline string is just POD - copy the bytes
203            Self {
204                _align: [],
205                disc: self.disc,
206                data_or_partial_ptr: self.data_or_partial_ptr,
207            }
208        } else {
209            // SAFETY: Since NonEmptySinStr was already constructed we know if it can fit
210            // in a `NonEmptySinStr` as inline or not. It wasn't an inline so its heap allocated.
211            unsafe { Self::new_heap(self.as_str()) }
212        }
213    }
214
215    // TODO: implement clone_from when capacity is tracked
216}
217
218impl Debug for NonEmptySinStr {
219    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
220        f.debug_struct("NonEmptySinStr")
221            .field("data_or_partial_ptr", &self.data_or_partial_ptr)
222            .field("disc", &self.disc)
223            .finish()
224    }
225}
226
227impl Display for NonEmptySinStr {
228    #[inline(always)]
229    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
230        <str as Display>::fmt(self.as_str(), f)
231    }
232}
233
234impl Hash for NonEmptySinStr {
235    #[inline(always)]
236    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
237        self.as_str().hash(state);
238    }
239}
240
241impl PartialEq for NonEmptySinStr {
242    #[inline(always)]
243    fn eq(&self, other: &Self) -> bool {
244        // We check storage mode first because inline strings can only hold
245        // lengths 1..=NICHE_MAX_INT while heap strings always have lengths
246        // > NICHE_MAX_INT. If storage modes differ, lengths must differ,
247        // so strings cannot be equal. This serves as a fast-path rejection.
248        self.is_inlined() == other.is_inlined() && self.as_str() == other.as_str()
249    }
250}
251impl Eq for NonEmptySinStr {}
252
253impl PartialOrd for NonEmptySinStr {
254    #[inline]
255    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
256        Some(self.cmp(other))
257    }
258}
259impl Ord for NonEmptySinStr {
260    #[inline]
261    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
262        self.as_str().cmp(other.as_str())
263    }
264}
265
266impl Deref for NonEmptySinStr {
267    type Target = str;
268    #[inline]
269    fn deref(&self) -> &Self::Target {
270        self.as_str()
271    }
272}
273impl DerefMut for NonEmptySinStr {
274    #[inline]
275    fn deref_mut(&mut self) -> &mut Self::Target {
276        self.as_str_mut()
277    }
278}
279
280impl AsRef<str> for NonEmptySinStr {
281    #[inline]
282    fn as_ref(&self) -> &str {
283        self.as_str()
284    }
285}
286
287impl AsRef<[u8]> for NonEmptySinStr {
288    #[inline]
289    fn as_ref(&self) -> &[u8] {
290        self.as_bytes()
291    }
292}
293
294impl Borrow<str> for NonEmptySinStr {
295    #[inline]
296    fn borrow(&self) -> &str {
297        self.as_str()
298    }
299}
300
301impl BorrowMut<str> for NonEmptySinStr {
302    #[inline]
303    fn borrow_mut(&mut self) -> &mut str {
304        self.as_str_mut()
305    }
306}
307
308impl NonEmptySinStr {
309    /// Create a new [`NonEmptySinStr`]
310    ///
311    /// Returns [`None`] if the string is empty.
312    #[inline(always)]
313    pub fn new(s: &str) -> Option<Self> {
314        let len = s.len();
315        if len == 0 {
316            return None;
317        }
318
319        Some(if likely(NICHE_MAX_INT >= len) {
320            // SAFETY: we have ensured `s` fits in an inline string
321            unsafe { Self::new_inline(s) }
322        } else {
323            // SAFETY: we have ensured `s` does not fit in an inline string and is not empty making
324            // it suitable for a heap string
325            unsafe { Self::new_heap(s) }
326        })
327    }
328
329    /// Create an inlined [`NonEmptySinStr`] at compile time.
330    ///
331    /// # Panics
332    ///
333    /// If the provided string is empty or longer than [`NICHE_MAX_INT`] bytes.
334    #[inline(always)]
335    pub const fn new_const(s: &str) -> Self {
336        if unlikely(s.is_empty()) {
337            panic!("Cannot construct empty NonEmptySinStr");
338        }
339
340        if unlikely(s.len() > NICHE_MAX_INT) {
341            panic!("Cannot construct string greater than inline capacity at compile time");
342        }
343
344        // SAFETY: Preconditions for new_inline have been checked above.
345        unsafe { Self::new_inline(s) }
346    }
347
348    /// Creates a new `SinStr` that stores data in the `SinStr` directly.
349    ///
350    /// # Safety
351    ///
352    /// The length of the provided string must be less than or equal to [`NICHE_MAX_INT`] but not
353    /// `0`.
354    #[inline(always)]
355    pub const unsafe fn new_inline(s: &str) -> Self {
356        let len = s.len();
357        debug_assert!(!HeapRepr::is_valid_len(len));
358        unsafe { assert_unchecked(!HeapRepr::is_valid_len(len)) };
359        let mut buf = [MaybeUninit::uninit(); size_of::<NonZeroUsize>() - 1];
360
361        // SAFETY: `len` must be less than or equal to or less than NICHE_MAX_INT but not equal to zero.
362        unsafe {
363            ptr::copy_nonoverlapping(s.as_ptr(), buf.as_mut_ptr().cast::<u8>(), len);
364        }
365
366        // SAFETY: len is less than or equal to NICHE_MAX_INT and all versions of DiscriminantValues have variants with that value.
367        unsafe {
368            NonEmptySinStr {
369                _align: [],
370                disc: transmute::<u8, DiscriminantValues>(len as u8),
371                data_or_partial_ptr: buf,
372            }
373        }
374    }
375
376    /// Creates a new `SinStr` that stores data on the heap.
377    ///
378    /// # Safety
379    ///
380    /// The length of the provided string must be greater than [`NICHE_MAX_INT`].
381    pub unsafe fn new_heap(s: &str) -> Self {
382        let len = s.len();
383        debug_assert!(HeapRepr::is_valid_len(len));
384        unsafe { assert_unchecked(HeapRepr::is_valid_len(len)) };
385        let total_size = size_of::<usize>() + len;
386        if unlikely(total_size > isize::MAX as usize) {
387            panic!("NonEmptySinStr::new_heap should never exceed max size");
388        }
389        let alloc_size = next_step(total_size);
390        // SAFETY: align_of::<usize>() is always valid (power of 2) and total_size > 0 because len > NICHE_MAX_INT > 0
391        let layout = unsafe { Layout::from_size_align_unchecked(alloc_size, align_of::<usize>()) };
392
393        // SAFETY: layout size > 0 because len > NICHE_MAX_INT > 0
394        let Some(ptr) = NonNull::new(unsafe { alloc(layout) }) else {
395            handle_alloc_error(layout)
396        };
397
398        // SAFETY: We allocated for a usize + len and the pointer is properly aligned.
399        unsafe {
400            ptr.cast::<usize>().write(len);
401            ptr.add(size_of::<usize>())
402                .copy_from_nonoverlapping(NonNull::new_unchecked(s.as_ptr() as *mut u8), len);
403            // SAFETY: Repr is #[repr(C)] and exactly size_of::<usize>() bytes.
404            transmute::<usize, NonEmptySinStr>(ptr.expose_provenance().get())
405        }
406    }
407
408    #[inline(always)]
409    pub const fn is_inlined(&self) -> bool {
410        // If the discriminant is less than NICHE_MAX_INT but greater than 0
411        // Then it means the pointer isn't properly aligned making it an inlined string.
412        //
413        // We are using the heap pointers alignment requirements as the niche to detect if we are inlined.
414        // If on the heap the LSB `NICHE_BITS` are always zero.
415        //
416        // This is also why we can't store empty strings in the inner repr as the length value is all zero bits.
417        let len = self.disc as usize;
418        // No branching since the sub just wraps
419        likely((len.wrapping_sub(1)) < NICHE_MAX_INT)
420    }
421
422    #[inline(always)]
423    pub const fn is_heap(&self) -> bool {
424        unlikely(!self.is_inlined())
425    }
426
427    #[inline(always)]
428    pub fn len(&self) -> NonZeroUsize {
429        if self.is_inlined() {
430            // SAFETY: is_inlined() guarantees the discriminant represents a valid
431            // inline length in range 1..=NICHE_MAX_INT, which is always non-zero.
432            unsafe { NonZeroUsize::new_unchecked(self.disc as usize) }
433        } else {
434            // SAFETY: we just checked if we had an inline string above
435            unsafe { self.get_heap() }.len()
436        }
437    }
438
439    /// Get the heap repr for the [`NonEmptySinStr`].
440    ///
441    /// # Safety
442    ///
443    /// Caller must ensure that the string is heap allocated.
444    #[inline(always)]
445    const unsafe fn get_heap(&self) -> &HeapRepr {
446        const _: () = assert!(size_of::<NonEmptySinStr>() == size_of::<usize>());
447        const _: () = assert!(align_of::<NonEmptySinStr>() == align_of::<usize>());
448        // SAFETY: self is a reference which is non null and HeapRepr has the same alignment as
449        // NonEmptySinStr.
450        //
451        // This is just a reference `transmute` that doesnt remove provenance.
452        unsafe {
453            (self as *const NonEmptySinStr as *const HeapRepr)
454                .as_ref()
455                .unwrap_unchecked()
456        }
457    }
458
459    /// Get the heap repr for the [`NonEmptySinStr`].
460    ///
461    /// # Safety
462    ///
463    /// Caller must ensure that the string is heap allocated.
464    #[inline(always)]
465    const unsafe fn get_heap_mut(&mut self) -> &mut HeapRepr {
466        const _: () = assert!(size_of::<NonEmptySinStr>() == size_of::<usize>());
467        // SAFETY: self is a reference which is non null and HeapRepr has the same alignment as
468        // NonEmptySinStr.
469        //
470        // This is just a reference `transmute` that doesnt remove provenance.
471        unsafe {
472            (self as *mut NonEmptySinStr as *mut HeapRepr)
473                .as_mut()
474                .unwrap_unchecked()
475        }
476    }
477
478    /// Get the inline repr for the [`NonEmptySinStr`].
479    ///
480    /// # Safety
481    ///
482    /// Caller must ensure that the string is inlined.
483    #[inline(always)]
484    const unsafe fn get_inlined(&self) -> &InlinedRepr {
485        // SAFETY: self is a reference which is non null and HeapRepr has the same alignment as
486        // NonEmptySinStr.
487        //
488        // This is just a reference `transmute` that doesnt remove provenance.
489        unsafe {
490            (self as *const NonEmptySinStr as *const InlinedRepr)
491                .as_ref()
492                .unwrap_unchecked()
493        }
494    }
495
496    /// Get the inline repr for the [`NonEmptySinStr`].
497    ///
498    /// # Safety
499    ///
500    /// Caller must ensure that the string is inlined.
501    #[inline(always)]
502    const unsafe fn get_inlined_mut(&mut self) -> &mut InlinedRepr {
503        // SAFETY: self is a reference which is non null and HeapRepr has the same alignment as
504        // NonEmptySinStr.
505        //
506        // This is just a reference `transmute` that doesnt remove provenance.
507        unsafe {
508            (self as *mut NonEmptySinStr as *mut InlinedRepr)
509                .as_mut()
510                .unwrap_unchecked()
511        }
512    }
513
514    /// Returns the string as a slice of bytes.
515    #[inline(always)]
516    pub const fn as_bytes(&self) -> &[u8] {
517        // SAFETY: just checked that the string is inlined
518        if likely(self.is_inlined()) {
519            unsafe { self.get_inlined() }.as_bytes()
520        } else {
521            unsafe { self.get_heap() }.as_bytes()
522        }
523    }
524
525    /// Returns the string as a mutable slice of bytes.
526    ///
527    /// # Safety
528    ///
529    /// After mutation, the bytes must remain valid UTF-8.
530    #[inline(always)]
531    pub const unsafe fn as_bytes_mut(&mut self) -> &mut [u8] {
532        if likely(self.is_inlined()) {
533            // SAFETY: string is inlined and as_byte_mut requirements are forwarded to caller.
534            unsafe { self.get_inlined_mut().as_bytes_mut() }
535        } else {
536            // SAFETY: string is not inlined and as_byte_mut requirements are forwarded to caller.
537            unsafe { self.get_heap_mut().as_bytes_mut() }
538        }
539    }
540
541    /// Returns the string as a `&str`.
542    #[inline(always)]
543    pub const fn as_str(&self) -> &str {
544        // SAFETY: just checked that the string is inlined
545        if likely(self.is_inlined()) {
546            unsafe { self.get_inlined() }.as_str()
547        } else {
548            // SAFETY: just checked that the string is not inlined
549            unsafe { self.get_heap() }.as_str()
550        }
551    }
552
553    /// Returns the string as a `&mut str`.
554    #[inline(always)]
555    pub const fn as_str_mut(&mut self) -> &mut str {
556        // SAFETY: just checked that the string is inlined
557        if likely(self.is_inlined()) {
558            unsafe { self.get_inlined_mut() }.as_str_mut()
559        } else {
560            // SAFETY: just checked that the string is not inlined
561            unsafe { self.get_heap_mut() }.as_str_mut()
562        }
563    }
564
565    /// Sets the content of [`NonEmptySinStr`] to `s`.
566    ///
567    /// # Panics
568    ///
569    /// The provided string must be non-empty.
570    /// Panics if `s.len() + size_of::<usize>()` is greated than [`isize::MAX`].
571    #[inline(always)]
572    pub fn set_str(&mut self, s: &str) {
573        if unlikely(s.is_empty()) {
574            panic!("NonEmptySinStr::set_str recieved empty string");
575        }
576
577        // SAFETY: we have checked that `s` isn't empty.
578        unsafe { self.set_str_unchecked(s) };
579    }
580
581    /// Sets the contents of [`NonEmptySinStr`] to `s`.
582    ///
583    /// Allocates or deallocates if needed.
584    /// This function will attempt to reuse an existing if possible so it is generally better than
585    /// reconstructing a new [`NonEmptySinStr`].
586    ///
587    /// # Safety
588    ///
589    /// The provided string must be non-empty.
590    ///
591    /// # Panics
592    ///
593    /// Panics if `s.len() + size_of::<usize>()` is greated than [`isize::MAX`].
594    #[inline(always)]
595    pub unsafe fn set_str_unchecked(&mut self, s: &str) {
596        debug_assert!(!s.is_empty());
597        unsafe { assert_unchecked(!s.is_empty()) };
598        if likely(s.len() <= NICHE_MAX_INT) {
599            // SAFETY: `s` fits in inline storage and it isn't empty
600            *self = unsafe { Self::new_inline(s) };
601        } else if self.is_heap() {
602            // SAFETY: `s` does not fit in inline storage and it isn't empty
603            let hp = unsafe { self.get_heap_mut() };
604            let next = next_step(size_of::<usize>() + s.len());
605            let capacity = hp.capacity();
606            if capacity.get() == next {
607                unsafe {
608                    hp.set_len(NonZeroUsize::new_unchecked(s.len()));
609                    hp.as_str_ptr_mut()
610                        .as_ptr()
611                        .copy_from_nonoverlapping(s.as_ptr(), s.len());
612                };
613            } else {
614                let new_size = next_step(size_of::<usize>() + s.len());
615                debug_assert!(new_size <= isize::MAX as usize);
616                // SAFETY: required by this function which is unsafe
617                unsafe { assert_unchecked(new_size <= isize::MAX as usize) };
618
619                // SAFETY:  alignment of usize is always valid and next_step returns a value that
620                // is a multiple of the alignment of usize. We also ensure that it is not greater
621                // than `isize::MAX` in the condition above.
622                let layout = unsafe {
623                    Layout::from_size_align_unchecked(hp.capacity().get(), align_of::<usize>())
624                };
625                let Some(ptr) = NonNull::new(
626                    // SAFETY:
627                    // - We allocated with the same Allocator
628                    // - Layout is the same
629                    // - `new_size` is never zero and is already a multiple of alignment
630                    unsafe { realloc(hp.as_ptr_mut().as_ptr() as *mut u8, layout, new_size) },
631                ) else {
632                    handle_alloc_error(layout);
633                };
634
635                hp.0 = ptr.expose_provenance();
636                // SAFETY: We have allocated enough space to store `s.len()`
637                // Both pointers are guaranteed to be non-null.
638                //
639                // `s.len()` bytes are now initialized so it is safe to call `HeapRepr::set_len`
640                unsafe {
641                    hp.as_str_ptr_mut()
642                        .as_ptr()
643                        .copy_from_nonoverlapping(s.as_ptr(), s.len());
644                    hp.set_len(NonZeroUsize::new_unchecked(s.len()));
645                };
646            }
647        } else {
648            // SAFETY: The first branch checks if we can inline the string.
649            // If not its a heap string.
650            // (we dont use `HeapRepr::is_valid_len` as zero length strings shouldn't reach this
651            // function anyways)
652            *self = unsafe { Self::new_heap(s) };
653        }
654    }
655}
656
657impl Drop for NonEmptySinStr {
658    #[inline]
659    fn drop(&mut self) {
660        if unlikely(self.is_heap()) {
661            // SAFETY: just checked that the string is on the heap
662            unsafe { self.drop_heap() };
663        }
664    }
665}
666
667impl NonEmptySinStr {
668    /// Deallocates the HeapRepr
669    ///
670    /// # Safety
671    ///
672    /// Caller must ensure that a [`HeapRepr`] is stored in self.
673    #[cold]
674    #[inline(always)]
675    unsafe fn drop_heap(&mut self) {
676        // SAFETY: we just ensured we are storing a heap string
677        // the layout is already validated during construction
678        // the pointer is always allocated with the same allocator
679        unsafe {
680            let heap = self.get_heap_mut();
681            let ptr = heap.as_ptr_mut();
682            let layout =
683                Layout::from_size_align_unchecked(heap.capacity().get(), align_of::<usize>());
684            dealloc(ptr.cast::<u8>().as_ptr(), layout)
685        }
686    }
687}
688
689// Ensure Repr and Option<Repr> is NPO
690const _: () = assert!(size_of::<NonEmptySinStr>() == size_of::<usize>());
691const _: () = assert!(size_of::<Option<NonEmptySinStr>>() == size_of::<usize>());
692const _: () = assert!(size_of::<Option<NonEmptySinStr>>() >= align_of::<usize>());
693
694const LEN_CAP_STEP: usize = align_of::<usize>();
695
696#[inline(always)]
697#[track_caller]
698const fn next_step(len: usize) -> usize {
699    // fast next_multiple_of for values that are power of 2
700    const _: () = assert!(LEN_CAP_STEP.count_ones() == 1);
701    let n = if NICHE_MAX_INT <= len {
702        len
703    } else {
704        const { NICHE_MAX_INT }
705    };
706
707    (n + LEN_CAP_STEP - 1) & !(LEN_CAP_STEP - 1)
708}
709
710#[cfg(test)]
711mod tests {
712    use super::*;
713    use crate::discriminant::NICHE_MAX_INT;
714    use alloc::string::String;
715
716    fn max_inline_string() -> String {
717        "x".repeat(NICHE_MAX_INT)
718    }
719
720    fn first_heap_string() -> String {
721        "y".repeat(NICHE_MAX_INT + 1)
722    }
723
724    mod constructor {
725        use super::*;
726
727        #[test]
728        fn test_new_returns_none_for_empty() {
729            assert!(NonEmptySinStr::new("").is_none());
730        }
731
732        #[test]
733        fn test_new_inline_string() {
734            for len in 1..=NICHE_MAX_INT {
735                let s = "a".repeat(len);
736                let nes = NonEmptySinStr::new(&s).expect("should create");
737                assert!(nes.is_inlined());
738                assert!(!nes.is_heap());
739                assert_eq!(nes.len().get(), len);
740                assert_eq!(nes.as_str(), s);
741            }
742        }
743
744        #[test]
745        fn test_new_heap_string() {
746            let s = first_heap_string();
747            let nes = NonEmptySinStr::new(&s).expect("should create");
748            assert!(!nes.is_inlined());
749            assert!(nes.is_heap());
750            assert_eq!(nes.len().get(), NICHE_MAX_INT + 1);
751            assert_eq!(nes.as_str(), s);
752        }
753
754        #[test]
755        fn test_new_inline_boundary() {
756            let max_inline = max_inline_string();
757            let nes = NonEmptySinStr::new(&max_inline).expect("should create");
758            assert!(nes.is_inlined());
759
760            let first_heap = first_heap_string();
761            let nes = NonEmptySinStr::new(&first_heap).expect("should create");
762            assert!(nes.is_heap());
763        }
764    }
765
766    mod unsafe_constructors {
767        use super::*;
768
769        #[test]
770        fn test_new_inline_valid() {
771            for len in 1..=NICHE_MAX_INT {
772                let s = "x".repeat(len);
773                let nes = unsafe { NonEmptySinStr::new_inline(&s) };
774                assert!(nes.is_inlined());
775                assert_eq!(nes.as_str(), s);
776            }
777        }
778
779        #[test]
780        fn test_new_heap_valid() {
781            let s = "a".repeat(NICHE_MAX_INT + 5);
782            let nes = unsafe { NonEmptySinStr::new_heap(&s) };
783            assert!(nes.is_heap());
784            assert_eq!(nes.as_str(), s);
785        }
786    }
787
788    mod storage_mode {
789        use super::*;
790
791        #[test]
792        fn test_is_heap_inline_string() {
793            let s = "a".repeat(NICHE_MAX_INT);
794            let nes = NonEmptySinStr::new(&s).expect("should create");
795            assert!(!nes.is_heap());
796        }
797
798        #[test]
799        fn test_is_heap_heap_string() {
800            let s = first_heap_string();
801            let nes = NonEmptySinStr::new(&s).expect("should create");
802            assert!(nes.is_heap());
803        }
804
805        #[test]
806        fn test_const_is_inlined() {
807            const S: NonEmptySinStr = unsafe { NonEmptySinStr::new_inline("a") };
808            assert_eq!(S.as_str(), "a");
809            assert!(S.is_inlined());
810        }
811    }
812
813    mod content_access {
814        use super::*;
815
816        #[test]
817        fn test_len_inline() {
818            for len in 1..=NICHE_MAX_INT.min(5) {
819                let s = "x".repeat(len);
820                let nes = NonEmptySinStr::new(&s).expect("should create");
821                assert_eq!(nes.len().get(), len);
822            }
823        }
824
825        #[test]
826        fn test_len_heap() {
827            let s = "x".repeat(100);
828            let nes = NonEmptySinStr::new(&s).expect("should create");
829            assert_eq!(nes.len().get(), 100);
830        }
831
832        #[test]
833        fn test_as_str_inline() {
834            let s = "hello";
835            if NICHE_MAX_INT >= 5 {
836                let nes = NonEmptySinStr::new(s).expect("should create");
837                assert_eq!(nes.as_str(), s);
838            }
839        }
840
841        #[test]
842        fn test_as_str_heap() {
843            let s = "hello world, this is a long string";
844            let nes = NonEmptySinStr::new(s).expect("should create");
845            assert_eq!(nes.as_str(), s);
846        }
847
848        #[test]
849        fn test_as_bytes_inline() {
850            let s = "abc";
851            if NICHE_MAX_INT >= 3 {
852                let nes = NonEmptySinStr::new(s).expect("should create");
853                assert_eq!(nes.as_bytes(), s.as_bytes());
854            }
855        }
856
857        #[test]
858        fn test_as_bytes_heap() {
859            let s = "longer string on the heap";
860            let nes = NonEmptySinStr::new(s).expect("should create");
861            assert_eq!(nes.as_bytes(), s.as_bytes());
862        }
863
864        #[test]
865        fn test_as_str_mut_inline() {
866            if NICHE_MAX_INT >= 3 {
867                let mut nes = NonEmptySinStr::new("abc").expect("should create");
868                let s_mut = nes.as_str_mut();
869                assert_eq!(s_mut, "abc");
870            }
871        }
872
873        #[test]
874        fn test_as_str_mut_heap() {
875            let original = "hello world";
876            let mut nes = NonEmptySinStr::new(original).expect("should create");
877            let s_mut = nes.as_str_mut();
878            assert_eq!(s_mut, original);
879        }
880
881        #[test]
882        fn test_as_bytes_mut_inline() {
883            if NICHE_MAX_INT >= 3 {
884                let mut nes = NonEmptySinStr::new("abc").expect("should create");
885                let bytes = unsafe { nes.as_bytes_mut() };
886                assert_eq!(bytes, b"abc");
887            }
888        }
889
890        #[test]
891        fn test_as_bytes_mut_heap() {
892            let original = "hello world";
893            let mut nes = NonEmptySinStr::new(original).expect("should create");
894            let bytes = unsafe { nes.as_bytes_mut() };
895            assert_eq!(bytes, original.as_bytes());
896        }
897    }
898
899    mod unsafe_accessors {
900        use super::*;
901
902        #[test]
903        fn test_get_inlined() {
904            let s = "ab";
905            if NICHE_MAX_INT >= 2 {
906                let nes = NonEmptySinStr::new(s).expect("should create");
907                let inlined = unsafe { nes.get_inlined() };
908                assert_eq!(inlined.as_str(), s);
909            }
910        }
911
912        #[test]
913        fn test_get_inlined_mut() {
914            let s = "xy";
915            if NICHE_MAX_INT >= 2 {
916                let mut nes = NonEmptySinStr::new(s).expect("should create");
917                let inlined = unsafe { nes.get_inlined_mut() };
918                assert_eq!(inlined.as_str(), s);
919            }
920        }
921
922        #[test]
923        fn test_get_heap() {
924            let s = first_heap_string();
925            let nes = NonEmptySinStr::new(&s).expect("should create");
926            let heap = unsafe { nes.get_heap() };
927            assert_eq!(heap.as_str(), &s);
928        }
929
930        #[test]
931        fn test_get_heap_mut() {
932            let s = first_heap_string();
933            let mut nes = NonEmptySinStr::new(&s).expect("should create");
934            let heap = unsafe { nes.get_heap_mut() };
935            assert_eq!(heap.as_str(), &s);
936        }
937
938        #[test]
939        fn test_heap_repr_len() {
940            let len = NICHE_MAX_INT + 10;
941            let s = "x".repeat(len);
942            let nes = NonEmptySinStr::new(&s).expect("should create");
943            let heap = unsafe { nes.get_heap() };
944            assert_eq!(heap.len().get(), len);
945        }
946    }
947
948    mod edge_cases {
949        use super::*;
950
951        #[test]
952        fn test_unicode_inline() {
953            let unicode_chars = [("Γ©", 2), ("ζ—₯", 3), ("πŸ¦€", 4)];
954            for (c, byte_len) in unicode_chars {
955                if NICHE_MAX_INT >= byte_len {
956                    let nes = NonEmptySinStr::new(c).expect("should create");
957                    assert!(nes.is_inlined());
958                    assert_eq!(nes.as_str(), c);
959                }
960            }
961        }
962
963        #[test]
964        fn test_unicode_heap() {
965            let s = "ζ—₯本θͺžγƒ†γ‚Ήγƒˆ";
966            let nes = NonEmptySinStr::new(s).expect("should create");
967            assert_eq!(nes.as_str(), s);
968            assert!(nes.is_heap());
969        }
970
971        #[test]
972        fn test_unicode_max_inline() {
973            if NICHE_MAX_INT >= 4 {
974                let s = "πŸ¦€".repeat(NICHE_MAX_INT / 4);
975                let nes = NonEmptySinStr::new(&s).expect("should create");
976                assert!(nes.is_inlined());
977                assert_eq!(nes.as_str(), s);
978            }
979        }
980
981        #[test]
982        fn test_very_long_heap_string() {
983            let s = "x".repeat(10000);
984            let nes = NonEmptySinStr::new(&s).expect("should create");
985            assert!(nes.is_heap());
986            assert_eq!(nes.len().get(), 10000);
987            assert_eq!(nes.as_str(), s);
988        }
989    }
990
991    mod trait_impls {
992        use super::*;
993        use alloc::borrow::Borrow;
994        use core::hash::{Hash, Hasher};
995
996        mod clone_tests {
997            use super::*;
998
999            #[test]
1000            fn test_clone_inline() {
1001                let s = "abc";
1002                if NICHE_MAX_INT >= 3 {
1003                    let nes = NonEmptySinStr::new(s).expect("should create");
1004                    let cloned = nes.clone();
1005                    assert_eq!(cloned.as_str(), s);
1006                    assert!(cloned.is_inlined());
1007                }
1008            }
1009
1010            #[test]
1011            fn test_clone_heap() {
1012                let s = first_heap_string();
1013                let nes = NonEmptySinStr::new(&s).expect("should create");
1014                let cloned = nes.clone();
1015                assert_eq!(cloned.as_str(), &s);
1016                assert!(cloned.is_heap());
1017            }
1018
1019            #[test]
1020            fn test_clone_max_inline() {
1021                let s = max_inline_string();
1022                let nes = NonEmptySinStr::new(&s).expect("should create");
1023                let cloned = nes.clone();
1024                assert_eq!(cloned.as_str(), &s);
1025                assert!(cloned.is_inlined());
1026            }
1027
1028            #[test]
1029            fn test_clone_preserves_content() {
1030                let original = "hello";
1031                if NICHE_MAX_INT >= 5 {
1032                    let nes = NonEmptySinStr::new(original).expect("should create");
1033                    let cloned = nes.clone();
1034                    assert_eq!(original, cloned.as_str());
1035                }
1036            }
1037        }
1038
1039        mod display_tests {
1040            use super::*;
1041
1042            #[test]
1043            fn test_display_inline() {
1044                let s = "hello";
1045                if NICHE_MAX_INT >= 5 {
1046                    let nes = NonEmptySinStr::new(s).expect("should create");
1047                    let displayed = alloc::format!("{}", nes);
1048                    assert_eq!(displayed, s);
1049                }
1050            }
1051
1052            #[test]
1053            fn test_display_heap() {
1054                let s = first_heap_string();
1055                let nes = NonEmptySinStr::new(&s).expect("should create");
1056                let displayed = alloc::format!("{}", nes);
1057                assert_eq!(displayed, s);
1058            }
1059
1060            #[test]
1061            fn test_display_unicode() {
1062                let s = "ζ—₯本θͺž";
1063                let nes = NonEmptySinStr::new(s).expect("should create");
1064                let displayed = alloc::format!("{}", nes);
1065                assert_eq!(displayed, s);
1066            }
1067        }
1068
1069        mod hash_tests {
1070            use super::*;
1071            extern crate std;
1072            use std::hash::DefaultHasher;
1073
1074            fn calculate_hash<T: Hash>(value: &T) -> u64 {
1075                let mut hasher = DefaultHasher::new();
1076                value.hash(&mut hasher);
1077                hasher.finish()
1078            }
1079
1080            #[test]
1081            fn test_hash_consistency() {
1082                let s = "abc";
1083                if NICHE_MAX_INT >= 3 {
1084                    let nes1 = NonEmptySinStr::new(s).expect("should create");
1085                    let nes2 = NonEmptySinStr::new(s).expect("should create");
1086                    assert_eq!(calculate_hash(&nes1), calculate_hash(&nes2));
1087                }
1088            }
1089
1090            #[test]
1091            fn test_hash_different() {
1092                if NICHE_MAX_INT >= 3 {
1093                    let nes1 = NonEmptySinStr::new("abc").expect("should create");
1094                    let nes2 = NonEmptySinStr::new("xyz").expect("should create");
1095                    assert_ne!(calculate_hash(&nes1), calculate_hash(&nes2));
1096                }
1097            }
1098
1099            #[test]
1100            fn test_hash_inline_vs_heap_different() {
1101                let short = "ab";
1102                let long_suffix = "x".repeat(NICHE_MAX_INT);
1103                let content = alloc::format!("{}{}", short, long_suffix);
1104
1105                let nes_heap = NonEmptySinStr::new(&content).expect("should create");
1106                let str_ref: &str = nes_heap.as_str();
1107                let nes_inline =
1108                    NonEmptySinStr::new(str_ref.get(0..2).unwrap()).expect("should create");
1109
1110                assert_ne!(
1111                    calculate_hash(&nes_heap),
1112                    calculate_hash(&nes_inline),
1113                    "Different content should have different hashes"
1114                );
1115            }
1116        }
1117
1118        mod eq_tests {
1119            use super::*;
1120
1121            #[test]
1122            fn test_eq_same_content_inline() {
1123                let s = "test";
1124                if NICHE_MAX_INT >= 4 {
1125                    let a = NonEmptySinStr::new(s).expect("should create");
1126                    let b = NonEmptySinStr::new(s).expect("should create");
1127                    assert_eq!(a, b);
1128                }
1129            }
1130
1131            #[test]
1132            fn test_eq_same_content_heap() {
1133                let s = first_heap_string();
1134                let a = NonEmptySinStr::new(&s).expect("should create");
1135                let b = NonEmptySinStr::new(&s).expect("should create");
1136                assert_eq!(a, b);
1137            }
1138
1139            #[test]
1140            fn test_ne_different_content() {
1141                if NICHE_MAX_INT >= 3 {
1142                    let a = NonEmptySinStr::new("abc").expect("should create");
1143                    let b = NonEmptySinStr::new("xyz").expect("should create");
1144                    assert_ne!(a, b);
1145                }
1146            }
1147
1148            #[test]
1149            fn test_eq_inline_and_heap_same_storage_mode_check() {
1150                let s1 = "a".repeat(NICHE_MAX_INT);
1151                let s2 = s1.clone();
1152                let a = NonEmptySinStr::new(&s1).expect("should create");
1153                let b = NonEmptySinStr::new(&s2).expect("should create");
1154                assert!(a.is_inlined());
1155                assert!(b.is_inlined());
1156                assert_eq!(a, b);
1157            }
1158        }
1159
1160        mod ord_tests {
1161            use super::*;
1162            use core::cmp::Ordering;
1163
1164            #[test]
1165            fn test_ord_less() {
1166                if NICHE_MAX_INT >= 2 {
1167                    let a = NonEmptySinStr::new("ab").expect("should create");
1168                    let b = NonEmptySinStr::new("cd").expect("should create");
1169                    assert_eq!(a.cmp(&b), Ordering::Less);
1170                    assert!(a < b);
1171                }
1172            }
1173
1174            #[test]
1175            fn test_ord_equal() {
1176                let s = "test";
1177                if NICHE_MAX_INT >= 4 {
1178                    let a = NonEmptySinStr::new(s).expect("should create");
1179                    let b = NonEmptySinStr::new(s).expect("should create");
1180                    assert_eq!(a.cmp(&b), Ordering::Equal);
1181                    assert!(a <= b);
1182                    assert!(a >= b);
1183                }
1184            }
1185
1186            #[test]
1187            fn test_ord_greater() {
1188                if NICHE_MAX_INT >= 2 {
1189                    let a = NonEmptySinStr::new("yz").expect("should create");
1190                    let b = NonEmptySinStr::new("ab").expect("should create");
1191                    assert_eq!(a.cmp(&b), Ordering::Greater);
1192                    assert!(a > b);
1193                }
1194            }
1195
1196            #[test]
1197            fn test_ord_cross_storage() {
1198                let short = "ab";
1199                let long = "cd";
1200                if NICHE_MAX_INT >= 2 && long.len() <= NICHE_MAX_INT {
1201                    let a = NonEmptySinStr::new(short).expect("should create");
1202                    let b = NonEmptySinStr::new(long).expect("should create");
1203                    assert!(a < b);
1204                }
1205            }
1206        }
1207
1208        mod deref_tests {
1209            use super::*;
1210
1211            #[test]
1212            fn test_deref_inline() {
1213                let s = "hello";
1214                if NICHE_MAX_INT >= 5 {
1215                    let nes = NonEmptySinStr::new(s).expect("should create");
1216                    assert_eq!(&*nes, s);
1217                }
1218            }
1219
1220            #[test]
1221            fn test_deref_heap() {
1222                let s = first_heap_string();
1223                let nes = NonEmptySinStr::new(&s).expect("should create");
1224                assert_eq!(&*nes, &s);
1225            }
1226
1227            #[test]
1228            fn test_deref_methods() {
1229                let s = "test";
1230                if NICHE_MAX_INT >= 4 {
1231                    let nes = NonEmptySinStr::new(s).expect("should create");
1232                    assert_eq!(nes.len().get(), s.len());
1233                    assert!(nes.starts_with("te"));
1234                    assert!(nes.ends_with("st"));
1235                }
1236            }
1237
1238            #[test]
1239            fn test_deref_mut_inline() {
1240                if NICHE_MAX_INT >= 3 {
1241                    let nes = NonEmptySinStr::new("abc").expect("should create");
1242                    assert_eq!(&*nes, "abc");
1243                }
1244            }
1245
1246            #[test]
1247            fn test_deref_mut_heap() {
1248                let s = first_heap_string();
1249                let nes = NonEmptySinStr::new(&s).expect("should create");
1250                assert_eq!(&*nes, &s);
1251            }
1252        }
1253
1254        mod as_ref_tests {
1255            use super::*;
1256
1257            #[test]
1258            fn test_as_ref_str_inline() {
1259                let s = "test";
1260                if NICHE_MAX_INT >= 4 {
1261                    let nes = NonEmptySinStr::new(s).expect("should create");
1262                    let as_str: &str = nes.as_ref();
1263                    assert_eq!(as_str, s);
1264                }
1265            }
1266
1267            #[test]
1268            fn test_as_ref_str_heap() {
1269                let s = first_heap_string();
1270                let nes = NonEmptySinStr::new(&s).expect("should create");
1271                let as_str: &str = nes.as_ref();
1272                assert_eq!(as_str, &s);
1273            }
1274
1275            #[test]
1276            fn test_as_ref_bytes_inline() {
1277                let s = "abc";
1278                if NICHE_MAX_INT >= 3 {
1279                    let nes = NonEmptySinStr::new(s).expect("should create");
1280                    let as_bytes: &[u8] = nes.as_ref();
1281                    assert_eq!(as_bytes, s.as_bytes());
1282                }
1283            }
1284
1285            #[test]
1286            fn test_as_ref_bytes_heap() {
1287                let s = first_heap_string();
1288                let nes = NonEmptySinStr::new(&s).expect("should create");
1289                let as_bytes: &[u8] = nes.as_ref();
1290                assert_eq!(as_bytes, s.as_bytes());
1291            }
1292        }
1293
1294        mod borrow_tests {
1295            use super::*;
1296
1297            #[test]
1298            fn test_borrow_str_inline() {
1299                let s = "test";
1300                if NICHE_MAX_INT >= 4 {
1301                    let nes = NonEmptySinStr::new(s).expect("should create");
1302                    let borrowed: &str = nes.borrow();
1303                    assert_eq!(borrowed, s);
1304                }
1305            }
1306
1307            #[test]
1308            fn test_borrow_str_heap() {
1309                let s = first_heap_string();
1310                let nes = NonEmptySinStr::new(&s).expect("should create");
1311                let borrowed: &str = nes.borrow();
1312                assert_eq!(borrowed, &s);
1313            }
1314
1315            #[test]
1316            fn test_borrow_mut_str_inline() {
1317                let s = "abc";
1318                if NICHE_MAX_INT >= 3 {
1319                    let mut nes = NonEmptySinStr::new(s).expect("should create");
1320                    let borrowed: &mut str = nes.borrow_mut();
1321                    assert_eq!(borrowed, s);
1322                }
1323            }
1324
1325            #[test]
1326            fn test_borrow_mut_str_heap() {
1327                let s = first_heap_string();
1328                let mut nes = NonEmptySinStr::new(&s).expect("should create");
1329                let borrowed: &mut str = nes.borrow_mut();
1330                assert_eq!(borrowed, &s);
1331            }
1332        }
1333    }
1334
1335    mod len_capacity {
1336        use crate::non_empty::{LEN_CAP_STEP, next_step};
1337
1338        #[test]
1339        fn forces_next_step() {
1340            // 0..=8
1341            assert_eq!(next_step(0), LEN_CAP_STEP);
1342            assert_eq!(next_step(1), LEN_CAP_STEP);
1343            assert_eq!(next_step(2), LEN_CAP_STEP);
1344            assert_eq!(next_step(3), LEN_CAP_STEP);
1345            assert_eq!(next_step(4), LEN_CAP_STEP);
1346            assert_eq!(next_step(5), LEN_CAP_STEP);
1347            assert_eq!(next_step(6), LEN_CAP_STEP);
1348            assert_eq!(next_step(7), LEN_CAP_STEP);
1349            assert_eq!(next_step(8), LEN_CAP_STEP);
1350
1351            // 9..=16
1352            assert_eq!(next_step(9), LEN_CAP_STEP * 2);
1353            assert_eq!(next_step(10), LEN_CAP_STEP * 2);
1354            assert_eq!(next_step(11), LEN_CAP_STEP * 2);
1355            assert_eq!(next_step(12), LEN_CAP_STEP * 2);
1356            assert_eq!(next_step(13), LEN_CAP_STEP * 2);
1357            assert_eq!(next_step(14), LEN_CAP_STEP * 2);
1358            assert_eq!(next_step(15), LEN_CAP_STEP * 2);
1359            assert_eq!(next_step(16), LEN_CAP_STEP * 2);
1360
1361            // 17..=24
1362            assert_eq!(next_step(17), LEN_CAP_STEP * 3);
1363            assert_eq!(next_step(18), LEN_CAP_STEP * 3);
1364            assert_eq!(next_step(19), LEN_CAP_STEP * 3);
1365            assert_eq!(next_step(20), LEN_CAP_STEP * 3);
1366            assert_eq!(next_step(21), LEN_CAP_STEP * 3);
1367            assert_eq!(next_step(22), LEN_CAP_STEP * 3);
1368            assert_eq!(next_step(23), LEN_CAP_STEP * 3);
1369            assert_eq!(next_step(24), LEN_CAP_STEP * 3);
1370        }
1371    }
1372
1373    mod set_str {
1374        use super::*;
1375
1376        #[test]
1377        fn test_set_str_inline_to_inline() {
1378            let mut nes = NonEmptySinStr::new("abc").expect("should create");
1379            nes.set_str("xyz");
1380            assert!(nes.is_inlined());
1381            assert_eq!(nes.as_str(), "xyz");
1382        }
1383
1384        #[test]
1385        fn test_set_str_inline_to_heap() {
1386            let mut nes = NonEmptySinStr::new("abc").expect("should create");
1387            let heap_str = "x".repeat(NICHE_MAX_INT + 10);
1388            nes.set_str(&heap_str);
1389            assert!(nes.is_heap());
1390            assert_eq!(nes.as_str(), heap_str);
1391        }
1392
1393        #[test]
1394        fn test_set_str_heap_to_inline() {
1395            let heap_str = "y".repeat(NICHE_MAX_INT + 10);
1396            let mut nes = NonEmptySinStr::new(&heap_str).expect("should create");
1397            nes.set_str("abc");
1398            assert!(nes.is_inlined());
1399            assert_eq!(nes.as_str(), "abc");
1400        }
1401
1402        #[test]
1403        fn test_set_str_heap_to_heap_smaller() {
1404            let heap_str = "x".repeat(NICHE_MAX_INT + 100);
1405            let mut nes = NonEmptySinStr::new(&heap_str).expect("should create");
1406            let smaller_heap = "y".repeat(NICHE_MAX_INT + 10);
1407            nes.set_str(&smaller_heap);
1408            assert!(nes.is_heap());
1409            assert_eq!(nes.as_str(), smaller_heap);
1410        }
1411
1412        #[test]
1413        fn test_set_str_heap_to_heap_larger() {
1414            let heap_str = "x".repeat(NICHE_MAX_INT + 10);
1415            let mut nes = NonEmptySinStr::new(&heap_str).expect("should create");
1416            let larger_heap = "z".repeat(NICHE_MAX_INT + 100);
1417            nes.set_str(&larger_heap);
1418            assert!(nes.is_heap());
1419            assert_eq!(nes.as_str(), larger_heap);
1420        }
1421
1422        #[test]
1423        fn test_set_str_preserves_content_max_inline() {
1424            let max_inline = "a".repeat(NICHE_MAX_INT);
1425            let mut nes = NonEmptySinStr::new(&max_inline).expect("should create");
1426            nes.set_str(&max_inline);
1427            assert!(nes.is_inlined());
1428            assert_eq!(nes.as_str(), max_inline);
1429        }
1430
1431        #[test]
1432        fn test_set_str_unicode() {
1433            let mut nes = NonEmptySinStr::new("ζ—₯本θͺž").expect("should create");
1434            nes.set_str("δΈ­ζ–‡ζ΅‹θ―•");
1435            assert_eq!(nes.as_str(), "δΈ­ζ–‡ζ΅‹θ―•");
1436
1437            let unicode_heap = "πŸ¦€".repeat(NICHE_MAX_INT / 4 + 10);
1438            nes.set_str(&unicode_heap);
1439            assert!(nes.is_heap());
1440            assert_eq!(nes.as_str(), unicode_heap);
1441        }
1442
1443        #[test]
1444        #[should_panic(expected = "NonEmptySinStr::set_str recieved empty string")]
1445        fn test_set_str_panic_on_empty() {
1446            let mut nes = NonEmptySinStr::new("abc").expect("should create");
1447            nes.set_str("");
1448        }
1449    }
1450}