Skip to main content

devela/text/str/array/
u.rs

1// devela::text::str::u
2//
3//! `String` backed by an array.
4//
5// TOC
6// - impl_str_u!
7//   - definitions
8//   - trait impls
9// - tests
10
11use crate::{AddAssign, ConstInit, Deref, Hash, Hasher, is, lets, paste, slice, whilst};
12use crate::{Char, CharIter, InvalidText, Str, StringNonul, char7, char8, char16, charu};
13#[allow(unused, reason = "±unsafe")]
14use crate::{Cmp, unwrap};
15use crate::{Debug, Display, FmtError, FmtResult, FmtWrite, Formatter, Ordering};
16use crate::{MismatchedCapacity, NotEnoughElements, NotEnoughSpace};
17
18macro_rules! impl_str_u {
19    // in sync with devela::code::const_init % _stringu
20    // () => { impl_str_u![u8, u16]; };
21    () => { impl_str_u![u8]; }; // TEMP
22    (
23    $($P:ty),+ $(,)?) => { paste! { $(
24        impl_str_u![%[<String $P:camel>], $P, [<NonMax $P:camel>], $crate::[<NonMax $P:camel>]];
25    )+ }};
26    (%
27    // $name: the name of the type. E.g.: StringU8.
28    // $P: the inner primitive length type. E.g.: u8.
29    // $ni: the niche length type. E.g.: NonMax8.
30    // $NI: the full path niche type. E.g.: $crate::NonMaxU8.
31    $name:ident, $P:ty, $ni:ty, $NI:ty) => {
32        /* definitions */
33
34        #[allow(rustdoc::broken_intra_doc_links, reason = "±unsafe")]
35        #[doc = crate::_tags!(string)]
36        /// A UTF-8 string with fixed capacity that stores length explicitly.
37        #[doc = crate::_doc_meta!{location("text/str")}]
38        ///
39        /// Suited for frequently inspected or manipulated text where constant-time
40        /// length access is important. Uses extra space to provide O(1) length operations.
41        /// For the opposite trade-off see [`StringNonul`].
42        ///
43        #[doc = concat!(
44            "Internally, the current length is stored as a [`",
45            stringify!($ni), "`][$crate::", stringify!($ni), "].")]
46        ///
47        /// ## Methods
48        ///
49        /// - [Constructors](#constructors):
50        ///   - [new](#method.new)
51        ///     *([_checked](#method.new_checked))*.
52        ///   - [from_str](#method.from_str),
53        ///     *([_truncate](#method.from_str_truncate),
54        ///       [_unchecked](#method.from_str_unchecked))*.
55        ///   - [from_char](#method.from_char)
56        ///     *([7](#method.from_char7),
57        ///       [8](#method.from_char8),
58        ///       [16](#method.from_char16),
59        ///       [utf8](#method.from_charu))*.
60        ///   - [from_array](#method.from_array) *(
61        ///     [_unchecked](#method.from_array_unchecked)<sup title='unsafe function'>⚠</sup>,
62        ///     [_nleft](#method.from_array_nleft),
63        ///     [_nleft_unchecked](#method.from_array_nleft_unchecked)<sup title='unsafe function'>⚠</sup>,
64        ///     [_nright](#method.from_array_nleft),
65        ///     [_nright_unchecked](#method.from_array_nright_unchecked)<sup title='unsafe function'>⚠</sup>)*.
66        ///
67        /// - [Deconstructors](#deconstructors):
68        ///   - [into_array](#method.into_array).
69        ///   - [as_array](#method.as_array).
70        ///   - [as_bytes](#method.as_bytes)
71        ///     *([mut](#method.as_bytes_mut)<sup title='unsafe function'>⚠</sup>)*.
72        ///   - [as_str](#method.as_str)
73        ///     *([mut](#method.as_mut_str)<sup title='unsafe function'>⚠</sup>)*.
74        ///   - [chars](#method.chars).
75        ///
76        /// - [Queries](#queries):
77        ///   - [len](#method.len).
78        ///   - [is_empty](#method.is_empty).
79        ///   - [is_full](#method.is_full).
80        ///   - [capacity](#method.capacity).
81        ///   - [remaining_capacity](#method.remaining_capacity).
82        ///
83        /// - [Modifiers](#modifiers):
84        ///   - [clear](#method.clear),
85        ///   - [reset](#method.reset),
86        ///   - [sanitize](#method.sanitize),
87        ///   - [pop](#method.pop)
88        ///     *([try_](#method.try_pop),
89        ///       [_unchecked](#method.pop_unchecked))*,
90        ///   - [push](#method.push)
91        ///     *([try_](#method.try_push))*.
92        ///   - [push_str](#method.push_str)
93        ///     *([try_](#method.try_push_str),
94        ///       [try_ _complete](#method.try_push_str_complete))*.
95        #[must_use]
96        #[derive(Clone, Copy, Eq)]
97        pub struct $name<const CAP: usize> {
98            arr: [u8; CAP], // WAIT: for when possible CAP:u8|u16 for panic-less boundary check
99            len: $crate::MaybeNiche<$NI>,
100            // len: $P, // BENCH non-niche len
101        }
102
103        /* helpers */
104        impl<const CAP: usize> $name<CAP> {
105            /* niche construction */
106            #[inline(always)]
107            const fn _ni_zero() -> $crate::MaybeNiche<$NI> {
108                // SAFETY-INVARIANT: `$NI` is a `NonMaxU*`; zero is always representable.
109                $crate::unwrap![some_guaranteed_or_ub $crate::MaybeNiche::<$NI>::ZERO]
110            }
111            #[inline(always)]
112            const fn _ni_prim(p: $P) -> $crate::MaybeNiche<$NI> {
113                $crate::unwrap![ok $crate::MaybeNiche::<$NI>::try_from_prim(p)]
114            }
115            #[inline(always)]
116            const fn _ni_usize(p: usize) -> $crate::MaybeNiche<$NI> {
117                $crate::unwrap![ok $crate::MaybeNiche::<$NI>::try_from_usize(p)]
118            }
119            /* cap validation */
120            #[inline(always)]
121            const fn _valid_cap() -> bool { CAP < <$P>::MAX as usize }
122            #[inline(always)]
123            const fn _assert_cap() {
124                assert![Self::_valid_cap(),
125                    concat!["Mismatched capacity, greater or equal than ", stringify![$P], "::MAX"]
126                ];
127            }
128            #[inline(always)]
129            const fn _check_cap() -> Result<(), MismatchedCapacity> {
130                if Self::_valid_cap() { Ok(()) }
131                else { Err(MismatchedCapacity::too_large(CAP, <$P>::MAX as usize - 1)) }
132            }
133            #[inline(always)]
134            const fn _check_cap_invalid_text() -> Result<(), InvalidText> {
135                if Self::_valid_cap() { Ok(()) } else {
136                    let err = MismatchedCapacity::too_large(CAP, <$P>::MAX as usize - 1);
137                    Err(InvalidText::from_mismatched_capacity(err)) }
138            }
139            /* len */
140            #[inline(always)]
141            const fn _add_len(&mut self, extra: usize) {
142                self.len = Self::_ni_usize(self.len() + extra); }
143            #[inline(always)]
144            const fn _len_prim(&self) -> $P { self.len.prim() }
145            #[inline(always)]
146            const fn _set_len_prim(&mut self, len: $P) { self.len = Self::_ni_prim(len); }
147            #[inline(always)]
148            const fn _set_len(&mut self, len: usize) { self.len = Self::_ni_usize(len); }
149            /* constructors */
150            #[inline(always)]
151            const fn _empty() -> Self { Self { arr: [0; CAP], len: Self::_ni_zero() } }
152            #[inline(always)]
153            const fn _from_parts_unchecked(arr: [u8; CAP], len: usize) -> Self {
154                Self { arr, len: Self::_ni_usize(len) }
155            }
156        }
157        // helper macro for constructors from chars
158        macro_rules! _str_u_copy_utf8 {
159            ($dst:expr, $src:expr, $len:expr; $N:tt) => {{
160                let dst = &mut $dst; let src = &$src; let len = $len;
161                $crate::punroll! { $N |i| if i < len { dst[i] = src[i]; } }
162            }};
163        }
164
165        /// # Constants
166        impl<const CAP: usize> $name<CAP> {
167            /// The maximum allowed capacity.
168            pub const MAX_CAPACITY: usize = <$P>::MAX as usize - 1;
169        }
170        /// # Constructors
171        impl<const CAP: usize> $name<CAP> { $crate::paste! {
172            /// Creates a new empty string from with a capacity of `CAP` bytes.
173            ///
174            /// # Panics
175            /// Panics if `CAP > Self::MAX_CAPACITY`.
176            ///
177            /// # Examples
178            /// ```
179            #[doc = "# use devela::" $name ";"]
180            #[doc = "let mut s = " $name "::<10>::new();"]
181            #[doc = "assert![size_of_val(&s) >= 10 + size_of::<" $P ">()]; // + padding"]
182            /// ```
183            pub const fn new() -> Self {
184                Self::_assert_cap();
185                Self::_empty()
186            }
187        }//paste!
188            /// Creates a new empty string from with a capacity of `CAP` bytes.
189            ///
190            /// # Errors
191            /// Returns [`MismatchedCapacity`] if `CAP > Self::MAX_CAPACITY`
192            pub const fn new_checked() -> Result<Self, MismatchedCapacity> {
193                match Self::_check_cap() {
194                    Ok(()) => Ok(Self::_empty()),
195                    Err(e) => Err(e),
196                }
197            }
198
199            /* from_str* conversions */
200
201            /// Creates a new string from a complete `&str`.
202            ///
203            /// # Errors
204            /// Returns [`MismatchedCapacity`] if `CAP > Self::MAX_CAPACITY`
205            /// or if `CAP < string.len()`.
206            ///
207            /// This is implemented via `Self::`[`try_push_str_complete()`][Self::try_push_str_complete].
208            ///
209            /// # Examples
210            /// ```
211            /// # use devela::StringU8;
212            /// let s = StringU8::<13>::from_str("Hello Wørld!").unwrap();
213            /// assert_eq![s.as_str(), "Hello Wørld!"];
214            /// ```
215            pub const fn from_str(string: &str) -> Result<Self, MismatchedCapacity> {
216                let mut new_string = unwrap![ok? Self::new_checked()];
217                if let Ok(_) = new_string.try_push_str_complete(string) { Ok(new_string) }
218                else { Err(MismatchedCapacity::too_small(CAP, string.len())) }
219            }
220
221            /// Creates a new string from a `&str`, truncating if it does not fit.
222            ///
223            /// Returns [`MismatchedCapacity`] if `CAP > Self::MAX_CAPACITY`
224            ///
225            /// This is implemented via `Self::`[`push_str()`][Self::push_str].
226            pub const fn from_str_truncate(string: &str) -> Result<Self, MismatchedCapacity> {
227                let mut new_string = unwrap![ok? Self::new_checked()];
228                let _ = new_string.push_str(string);
229                Ok(new_string)
230            }
231
232            /// Creates a new string from a `&str`, truncating if it does not fit.
233            ///
234            /// # Panics
235            /// Panics if `CAP > Self::MAX_CAPACITY`.
236            ///
237            /// This is implemented via `Self::`[`push_str()`][Self::push_str].
238            pub const fn from_str_unchecked(string: &str) -> Self {
239                let mut new_string = Self::new();
240                let _ = new_string.push_str(string);
241                new_string
242            }
243
244            /* from_char* conversions */
245
246            /// Creates a new string from a `char`.
247            ///
248            /// # Errors
249            /// Returns [`MismatchedCapacity`] if `CAP > Self::MAX_CAPACITY`
250            /// or if `CAP < c.`[`len_utf8()`][crate::UnicodeScalar::len_utf8].
251            ///
252            /// Will always succeed if `CAP >= 4 && CAP <= Self::MAX_CAPACITY`.
253            /// # Examples
254            /// ```
255            /// # use devela::{StringU8, char};
256            /// assert_eq![StringU8::<4>::from_char('🐛').unwrap().as_str(), "🐛"];
257            /// assert![StringU8::<3>::from_char('🐛').is_err()];
258            /// ```
259            pub const fn from_char(c: char) -> Result<Self, MismatchedCapacity> {
260                let bytes = Char(c).to_utf8_bytes();
261                let len = Char(bytes[0]).len_utf8_unchecked();
262                is![CAP < len, return Err(MismatchedCapacity::too_small(CAP, len))];
263                let mut new = unwrap![ok? Self::new_checked()];
264                _str_u_copy_utf8!(new.arr, bytes, len; 4);
265                new.len = Self::_ni_usize(len);
266                Ok(new)
267            }
268
269            /// Creates a new string from a `char7`.
270            ///
271            /// # Errors
272            /// Returns [`MismatchedCapacity`] if `CAP > Self::MAX_CAPACITY`
273            /// or if `CAP < 1`.
274            ///
275            /// Will always succeed if `CAP >= 1 && CAP <= Self::MAX_CAPACITY`.
276            /// # Examples
277            /// ```
278            /// # use devela::{StringU8, char7};
279            /// let s = StringU8::<1>::from_char7(char7::try_from_char('@').unwrap()).unwrap();
280            /// assert_eq![s.as_str(), "@"];
281            ///
282            /// assert![StringU8::<0>::from_char7(char7::try_from_char('@').unwrap()).is_err()];
283            /// ```
284            pub const fn from_char7(c: char7) -> Result<Self, MismatchedCapacity> {
285                is![CAP == 0, return Err(MismatchedCapacity::too_small(CAP, 1))];
286                let mut new = unwrap![ok? Self::new_checked()];
287                new.arr[0] = c.to_utf8_bytes()[0];
288                new.len = Self::_ni_prim(1);
289                Ok(new)
290            }
291
292            /// Creates a new string from a `char8`.
293            ///
294            /// # Errors
295            /// Returns [`MismatchedCapacity`] if `CAP > Self::MAX_CAPACITY`
296            /// or if `CAP < 2.
297            ///
298            /// Will always succeed if `CAP >= 2 && CAP <= Self::MAX_CAPACITY`.
299            /// # Examples
300            /// ```
301            /// # use devela::{StringU8, char8};
302            /// let s = StringU8::<2>::from_char8(char8::try_from_char('ß').unwrap()).unwrap();
303            /// assert_eq![s.as_str(), "ß"];
304            ///
305            /// assert![StringU8::<1>::from_char8(char8::try_from_char('ß').unwrap()).is_err()];
306            /// ```
307            pub const fn from_char8(c: char8) -> Result<Self, MismatchedCapacity> {
308                let bytes = c.to_utf8_bytes();
309                let len = Char(bytes[0]).len_utf8_unchecked();
310                is![CAP < len, return Err(MismatchedCapacity::too_small(CAP, len))];
311                let mut new = unwrap![ok? Self::new_checked()];
312                _str_u_copy_utf8!(new.arr, bytes, len; 2);
313                new._set_len(len);
314                Ok(new)
315            }
316
317            /// Creates a new string from a `char16`.
318            ///
319            /// # Errors
320            /// Returns [`MismatchedCapacity`] if `CAP > Self::MAX_CAPACITY`
321            /// || `CAP < c.`[`len_utf8()`][char16#method.len_utf8]."]
322            ///
323            /// Will always succeed if `CAP >= 3 && CAP <= Self::MAX_CAPACITY`.
324            /// # Examples
325            /// ```
326            /// # use devela::{StringU8, char16};
327            /// let s = StringU8::<3>::from_char16(char16::try_from_char('€').unwrap()).unwrap();
328            /// assert_eq![s.as_str(), "€"];
329            ///
330            /// assert![StringU8::<2>::from_char16(char16::try_from_char('€').unwrap()).is_err()];
331            /// ```
332            pub const fn from_char16(c: char16) -> Result<Self, MismatchedCapacity> {
333                let bytes = c.to_utf8_bytes();
334                let len = Char(bytes[0]).len_utf8_unchecked();
335                is![CAP < len, return Err(MismatchedCapacity::too_small(CAP, len))];
336                let mut new = unwrap![ok? Self::new_checked()];
337                _str_u_copy_utf8!(new.arr, bytes, len; 3);
338                new._set_len(len);
339                Ok(new)
340            }
341
342            /// Creates a new string from a `charu`.
343            ///
344            /// # Errors
345            /// Returns [`MismatchedCapacity`] if `CAP > Self::MAX_CAPACITY`
346            /// || `CAP < c.`[`len_utf8()`][charu#method.len_utf8]."]
347            ///
348            /// Will always succeed if `CAP >= 4 && CAP <= Self::MAX_CAPACITY`.
349            /// # Examples
350            /// ```
351            /// # use devela::{StringU8, charu};
352            /// let s = StringU8::<4>::from_charu(charu::from_char('🐛')).unwrap();
353            /// assert_eq![s.as_str(), "🐛"];
354            ///
355            /// assert![StringU8::<3>::from_charu(charu::from_char('🐛')).is_err()];
356            /// ```
357            pub const fn from_charu(c: charu) -> Result<Self, MismatchedCapacity> {
358                let (bytes, len) = (c.to_utf8_bytes(), c.len_utf8());
359                if len <= CAP {
360                    let mut new = unwrap![ok? Self::new_checked()];
361                    _str_u_copy_utf8!(new.arr, bytes, len; 4);
362                    new._set_len(len);
363                    Ok(new)
364                } else {
365                    Err(MismatchedCapacity::too_small(CAP, len))
366                }
367            }
368
369            /// Creates a new string from a `charu`.
370            ///
371            /// # Panics
372            /// Panics if `CAP > Self::MAX_CAPACITY`
373            /// || `CAP < c.`[`len_utf8()`][charu#method.len_utf8].
374            ///
375            /// Will always succeed if `CAP >= 3 && CAP <= Self::MAX_CAPACITY`.
376            /// # Examples
377            /// ```
378            /// # use devela::{StringU8, charu};
379            /// let s = StringU8::<3>::from_charu_unchecked(charu::from_char('€'));
380            /// assert_eq![s, "€"]
381            /// ```
382            /// ```should_panic
383            /// # use devela::{StringU8, charu};
384            /// StringU8::<2>::from_charu_unchecked(charu::from_char('€'));
385            /// ```
386            pub const fn from_charu_unchecked(c: charu) -> Self {
387                let (bytes, len) = (c.to_utf8_bytes(), c.len_utf8());
388                let mut new = Self::new();
389                _str_u_copy_utf8!(new.arr, bytes, len; 4);
390                new._set_len(len);
391                new
392            }
393
394            /* from_array* conversions */
395
396            /// Returns a string from a slice of `bytes`.
397            ///
398            /// # Errors
399            /// Returns [`InvalidText::MismatchedCapacity`] if `CAP > Self::MAX_CAPACITY`
400            /// and [`InvalidText::Utf8`] if `bytes` are not valid UTF-8.
401            pub const fn from_array(bytes: [u8; CAP]) -> Result<Self, InvalidText> {
402                $crate::unwrap![ok? Self::_check_cap_invalid_text()];
403                match Str::from_utf8(&bytes) {
404                    Ok(_) => { Ok(Self::_from_parts_unchecked(bytes, CAP)) },
405                    Err(e) => Err(InvalidText::from_invalid_utf8(e)),
406                }
407            }
408
409            /// Returns a string from an array of `bytes` that must be valid UTF-8.
410            ///
411            /// # Panics
412            /// Panics if `CAP > Self::MAX_CAPACITY`.
413            /// # Safety
414            /// The caller must ensure that the content of the slice is valid UTF-8.
415            ///
416            /// Use of a `str` whose contents are not valid UTF-8 is undefined behavior.
417            #[cfg(all(not(feature = "safe_text"), feature = "unsafe_str"))]
418            #[cfg_attr(nightly_doc, doc(cfg(feature = "unsafe_str")))]
419            pub const unsafe fn from_array_unchecked(bytes: [u8; CAP]) -> Self {
420                Self::_assert_cap();
421                Self::_from_parts_unchecked(bytes, CAP)
422            }
423            /// Internal accessor for trusted formatting operations, avoiding UTF-8 re-validation.
424            /// # Panics
425            /// Panics if `CAP > Self::MAX_CAPACITY` or if `len > CAP`.
426            #[inline(always)]
427            pub(crate) const fn _from_array_len_trusted(array: [u8; CAP], len: $P) -> Self {
428                Self::_assert_cap();
429                assert!(len as usize <= CAP, "length greater than capacity");
430                Self::_from_parts_unchecked(array, len as usize)
431            }
432
433            /// Returns a string from an array of `bytes`,
434            /// truncated to `n` bytes counting from the left.
435            ///
436            /// The new `length` is maxed out at `CAP`.
437            ///
438            /// # Errors
439            /// Returns [`InvalidText::MismatchedCapacity`] if `CAP > Self::MAX_CAPACITY`
440            /// and [`InvalidText::Utf8`] if `bytes` are not valid UTF-8.
441            pub const fn from_array_nleft(bytes: [u8; CAP], length: $P)
442            -> Result<Self, InvalidText> {
443                $crate::unwrap![ok? Self::_check_cap_invalid_text()];
444                let length = Cmp(length).min(CAP as $P);
445                match Str::from_utf8(slice![&bytes, ..length as usize]) {
446                    Ok(_) => Ok(Self { arr: bytes, len: Self::_ni_prim(length) }),
447                    Err(e) => Err(InvalidText::from_invalid_utf8(e)),
448                }
449            }
450
451            /// Returns a string from an array of `bytes`, which must be valid UTF-8,
452            /// truncated to `n` bytes counting from the left.
453            ///
454            /// The new `length` is maxed out at `CAP`.
455            ///
456            /// # Panics
457            /// Panics if `CAP > Self::MAX_CAPACITY`.
458            ///
459            /// # Safety
460            /// The caller must ensure that the content of the truncated slice is valid UTF-8.
461            ///
462            /// Use of a `str` whose contents are not valid UTF-8 is undefined behavior.
463            #[cfg(all(not(feature = "safe_text"), feature = "unsafe_str"))]
464            #[cfg_attr(nightly_doc, doc(cfg(feature = "unsafe_str")))]
465            pub const unsafe fn from_array_nleft_unchecked(bytes: [u8; CAP], length: $P) -> Self {
466                Self::_assert_cap();
467                let len = Cmp(length).min(CAP as $P);
468                Self { arr: bytes, len: Self::_ni_prim(len) }
469            }
470
471            /// Returns a string from an array of `bytes`,
472            /// truncated to `n` bytes counting from the right.
473            ///
474            /// The new `length` is maxed out at `CAP`.
475            /// Bytes are shift-copied without allocating a new array.
476            ///
477            /// # Errors
478            /// Returns [`InvalidText::MismatchedCapacity`] if `CAP > Self::MAX_CAPACITY`
479            /// and [`InvalidText::Utf8`] if `bytes` are not valid UTF-8.
480            pub const fn from_array_nright(mut bytes: [u8; CAP], length: $P)
481            -> Result<Self, InvalidText> {
482                $crate::unwrap![ok? Self::_check_cap_invalid_text()];
483                let length = Cmp(length).min(CAP as $P);
484                let ulen = length as usize;
485                let start = CAP - ulen;
486                whilst![i in 0..ulen; bytes[i] = bytes[start + i]];
487                match Str::from_utf8(slice![&bytes, ..ulen]) {
488                    Ok(_) => Ok(Self { arr: bytes, len: Self::_ni_prim(length) }),
489                    Err(e) => Err(InvalidText::from_invalid_utf8(e)),
490                }
491            }
492
493            /// Returns a string from an array of `bytes`, which must be valid UTF-8,
494            /// truncated to `n` bytes counting from the right.
495            ///
496            /// The new `length` is maxed out at `CAP`.
497            /// Bytes are shift-copied without allocating a new array.
498            ///
499            /// # Panics
500            /// Panics if `CAP > Self::MAX_CAPACITY`.
501            ///
502            /// # Safety
503            /// The caller must ensure that the content of the truncated slice is valid UTF-8.
504            ///
505            /// Use of a `str` whose contents are not valid UTF-8 is undefined behavior.
506            #[cfg(all(not(feature = "safe_text"), feature = "unsafe_str"))]
507            #[cfg_attr(nightly_doc, doc(cfg(feature = "unsafe_str")))]
508            pub const unsafe fn from_array_nright_unchecked(mut bytes: [u8; CAP], length: $P)
509                -> Self {
510                Self::_assert_cap();
511                let length = Cmp(length).min(CAP as $P);
512                let ulen = length as usize;
513                let start = CAP - ulen;
514                whilst![i in 0..ulen; bytes[i] = bytes[start + i]];
515                Self { arr: bytes, len: Self::_ni_prim(length) }
516            }
517        }
518
519        /// # Deconstructors
520        impl<const CAP: usize> $name<CAP> {
521            /// Returns the inner array with the full contents.
522            ///
523            /// The array contains all the bytes, including those outside the current length.
524            #[must_use] #[inline(always)]
525            pub const fn into_array(self) -> [u8; CAP] { self.arr }
526
527            /// Returns a copy of the inner array with the full contents.
528            ///
529            /// The array contains all the bytes, including those outside the current length.
530            #[must_use] #[inline(always)]
531            pub const fn as_array(&self) -> &[u8; CAP] { &self.arr }
532
533            /// Returns a byte slice of the inner string slice.
534            ///
535            /// # Features
536            /// Uses the `unsafe_slice` feature to skip validation checks.
537            #[must_use] #[inline(always)]
538            pub const fn as_bytes(&self) -> &[u8] {
539                cfg_select! { all(feature = "unsafe_slice", not(feature = "safe_text")) => {
540                    // SAFETY: we ensure to contain a correct length
541                    unsafe { slice![unchecked &self.arr, ..self.len()] }
542                } _ => { slice![&self.arr, ..self.len()] }}
543            }
544
545            /// Returns an exclusive byte slice of the inner string slice.
546            ///
547            /// # Safety
548            /// The caller must ensure that the content of the slice is valid UTF-8
549            /// before the borrow ends and the underlying `str` is used.
550            ///
551            /// # Features
552            /// Uses the `unsafe_slice` feature to skip validation checks.
553            #[must_use] #[inline(always)]
554            #[cfg(all(not(feature = "safe_text"), feature = "unsafe_str"))]
555            #[cfg_attr(nightly_doc, doc(cfg(feature = "unsafe_str")))]
556            pub const unsafe fn as_bytes_mut(&mut self) -> &mut [u8] {
557                let len = self.len();
558                cfg_select! { all(feature = "unsafe_slice", not(feature = "safe_text")) => {
559                    // SAFETY: we ensure to contain a correct length
560                    unsafe { slice![mut_unchecked &mut self.arr, ..len] }
561                } _ => { slice![mut &mut self.arr, ..len] }}
562            }
563
564            /// Returns a reference to the inner string slice.
565            ///
566            /// # Features
567            /// Uses the `unsafe_str` feature to skip validation checks.
568            #[must_use]
569            #[inline(always)]
570            pub const fn as_str(&self) -> &str {
571                cfg_select! { all(feature = "unsafe_str", not(feature = "safe_text")) => {
572                    // SAFETY: we ensure to contain only valid UTF-8
573                    unsafe { Str::from_utf8_unchecked(self.as_bytes()) }
574                } _ => { unwrap![ok_expect Str::from_utf8(self.as_bytes()), "Invalid UTF-8"] }}
575            }
576
577            /// Returns an exclusive reference to the inner string slice.
578            ///
579            /// # Safety
580            /// The caller must ensure that the content of the slice is valid UTF-8
581            /// before the borrow ends and the underlying `str` is used.
582            #[must_use] #[inline(always)]
583            #[cfg(all(not(feature = "safe_text"), feature = "unsafe_str"))]
584            #[cfg_attr(nightly_doc, doc(cfg(feature = "unsafe_str")))]
585            pub const unsafe fn as_mut_str(&mut self) -> &mut str {
586                // SAFETY: we ensure to contain only valid UTF-8
587                unsafe { Str::from_utf8_unchecked_mut(self.as_bytes_mut()) }
588            }
589
590            /// Returns an iterator over the `chars` of the string.
591            ///
592            /// # Features
593            /// Uses the `unsafe_str` feature to skip validation checks.
594            #[inline(always)]
595            pub const fn chars(&self) -> CharIter<'_, &str> {
596                CharIter::<&str>::new(self.as_str())
597            }
598        }
599
600        /// # Queries
601        impl<const CAP: usize> $name<CAP> {
602            /// Returns the current string length in bytes.
603            #[must_use]
604            #[inline(always)]
605            pub const fn len(&self) -> usize { self.len.prim() as usize }
606
607            /// Returns `true` if the current length is 0.
608            #[must_use]
609            #[inline(always)]
610            pub const fn is_empty(&self) -> bool { self.len.prim() == 0 }
611
612            /// Returns `true` if the current remaining capacity is 0.
613            #[must_use]
614            #[inline(always)]
615            pub const fn is_full(&self) -> bool { self.len() == CAP }
616
617            /// Returns the total capacity in bytes.
618            #[must_use]
619            #[inline(always)]
620            pub const fn capacity() -> usize { CAP }
621
622            /// Returns the remaining capacity in bytes.
623            #[must_use]
624            #[inline(always)]
625            pub const fn remaining_capacity(&self) -> usize { CAP - self.len() }
626
627            /// Checks the equality of two strings, with the same capacity and length.
628            ///
629            /// It only checks the first `self.len()` bytes.
630            /// # Examples
631            /// ```
632            /// # use devela::StringU8;
633            /// let mut a = StringU8::<16>::from_str_unchecked("hello world!");
634            /// let mut b = StringU8::<16>::from_str_unchecked("hello world!!!");
635            /// assert![!a.eq(&b)];
636            /// b.pop();
637            /// b.pop();
638            /// assert![a.eq(&b)];
639            /// ```
640            #[must_use]
641            #[inline(always)]
642            pub const fn eq(&self, other: &Self) -> bool {
643                self.len.prim() == other.len.prim() && {
644                    whilst![i in 0..self.len(); is![self.arr[i] != other.arr[i], return false]];
645                    true
646                }
647            }
648        }
649
650        /// # Modifiers
651        impl<const CAP: usize> $name<CAP> {
652            /// Sets the length to 0.
653            #[inline(always)]
654            pub const fn clear(&mut self) { self.len = Self::_ni_zero() }
655
656            /// Sets the length to 0, and resets all the bytes to 0.
657            #[inline(always)]
658            pub const fn reset(&mut self) { self.arr = [0; CAP]; self.len = Self::_ni_zero(); }
659
660            /// Zeros all unused bytes while maintaining the current length.
661            #[inline(always)]
662            pub const fn sanitize(&mut self) {
663                whilst![i in (self.len()),..CAP; self.arr[i] = 0];
664            }
665
666            /// Removes the last character and returns it, or `None` if the string is empty.
667            #[must_use]
668            pub const fn pop(&mut self) -> Option<char> {
669                if self.is_empty() { None } else { Some(self.pop_unchecked()) }
670            }
671
672            /// Tries to remove the last character and returns it.
673            ///
674            /// # Errors
675            /// Returns a [`NotEnoughElements`] error
676            /// if the capacity is not enough to hold the `character`.
677            pub const fn try_pop(&mut self) -> Result<char, NotEnoughElements> {
678                is![self.is_empty(), Err(NotEnoughElements(Some(1))), Ok(self.pop_unchecked())]
679            }
680
681            /// Removes the last character and returns it.
682            ///
683            /// # Panics
684            /// Panics if the string is empty.
685            ///
686            /// # Examples
687            /// ```
688            /// # use devela::StringU8;
689            /// let mut s = StringU8::<16>::new();
690            /// s.push_str("hello worlð!");
691            /// assert_eq![s.len(), 13];
692            ///
693            /// assert_eq![s.pop_unchecked(), '!'];
694            /// assert_eq![s.len(), 12];
695            ///
696            /// assert_eq![s.pop_unchecked(), 'ð'];
697            /// assert_eq![s.len(), 10];
698            /// ```
699            pub const fn pop_unchecked(&mut self) -> char {
700                let string = self.as_str();
701                let mut index = string.len();
702                whilst![index > 0 && !string.is_char_boundary(index - 1); index -= 1];
703                let idx_last_char = index - 1;
704                let range = Str::range_from(string, idx_last_char);
705                let last_char = unwrap![some CharIter::<&str>::new(range).next_char()];
706                let new_len = self.len.prim() - last_char.len_utf8() as $P;
707                self.len = Self::_ni_prim(new_len);
708                last_char
709            }
710
711            /// Appends to the end of the string the given `character`.
712            ///
713            /// Returns the number of bytes written.
714            ///
715            /// Returns 0 bytes if the given `character` doesn't fit in the remaining capacity.
716            pub const fn push(&mut self, character: char) -> usize {
717                match self.try_push(character) { Ok(n) => n, Err(_) => 0 }
718            }
719
720            /// Tries to append to the end of the string the given `character`.
721            ///
722            /// Returns the number of bytes written.
723            ///
724            /// # Errors
725            /// Returns [`NotEnoughSpace`]
726            /// if the available capacity is not enough to hold the given `character`.
727            pub const fn try_push(&mut self, character: char) -> Result<usize, NotEnoughSpace> {
728                let (start, char_len) = (self.len(), character.len_utf8());
729                let end = start + char_len;
730                if end <= CAP {
731                    let _ = character.encode_utf8(slice![mut &mut self.arr, start, ..end]);
732                    self._set_len(end);
733                    Ok(char_len)
734                } else {
735                    Err(NotEnoughSpace(Some(char_len)))
736                }
737            }
738
739            /// Appends to the end of the string the given character.
740            ///
741            /// Returns the number of bytes written.
742            ///
743            /// Returns 0 bytes if the given `character` doesn't fit in the remaining capacity.
744            pub const fn push_charu(&mut self, c: charu) -> usize {
745                let (bytes, len) = (c.to_utf8_bytes(), c.len_utf8());
746                if self.remaining_capacity() >= len {
747                    let start = self.len();
748                    let end = start + len;
749                    slice![mut &mut self.arr, start,..end].copy_from_slice(slice![&bytes, 0,..len]);
750                    self.len = Self::_ni_usize(end);
751                    len
752                } else {
753                    0
754                }
755            }
756
757            /// Appends as many complete characters from `string` as will fit.
758            ///
759            /// Returns the number of bytes written. UTF-8 characters are never split.
760            ///
761            /// # Examples
762            /// ```
763            /// # use devela::StringU8;
764            /// let mut s = StringU8::<5>::new();
765            /// assert_eq!(s.push_str("café"), 5);
766            /// assert_eq!(s, "café");
767            ///
768            /// let mut s = StringU8::<4>::new();
769            /// assert_eq!(s.push_str("café"), 3);
770            /// assert_eq!(s, "caf");
771            ///
772            /// let mut s = StringU8::<2>::new();
773            /// assert_eq!(s.push_str("サ"), 0);
774            /// assert_eq!(s, "");
775            /// ```
776            pub const fn push_str(&mut self, string: &str) -> usize {
777                lets! { start = self.len(); remaining = CAP - start }
778                is! { remaining == 0, return 0 }
779                let string_len = string.len();
780                let bytes_to_write = if string_len <= remaining {
781                    string_len
782                } else {
783                    let mut amount = remaining;
784                    while amount > 0 && !string.is_char_boundary(amount) { amount -= 1; }
785                    amount
786                };
787                if bytes_to_write > 0 {
788                    let end = start + bytes_to_write;
789                    slice![mut &mut self.arr, start, ..end]
790                        .copy_from_slice(slice![string.as_bytes(), ..bytes_to_write]);
791                    self._set_len(end);
792                    bytes_to_write
793                } else {
794                    0
795                }
796            }
797
798            /// Appends characters from `string`, returning `Ok` if all fit, `Err` if partial.
799            ///
800            /// - `Ok(bytes)`: Entire string was written successfully
801            /// - `Err(partial)`: Only `partial` bytes could be written (UTF-8 safe)
802            ///
803            /// In both cases, the bytes are appended to the buffer.
804            pub const fn try_push_str(&mut self, string: &str) -> Result<usize, usize> {
805                let bytes_written = self.push_str(string);
806                is![bytes_written == string.len(), Ok(bytes_written), Err(bytes_written)]
807            }
808
809            /// Appends the entire `string` or nothing at all.
810            ///
811            /// Returns `Ok(bytes)` if the string fits completely, or `Err(0)` if it doesn't.
812            /// No partial writes will occur to the buffer.
813            pub const fn try_push_str_complete(&mut self, string: &str) -> Result<usize, usize> {
814                is![self.remaining_capacity() >= string.len(), Ok(self.push_str(string)), Err(0)]
815            }
816        }
817
818        /* utility traits */
819
820        impl<const CAP: usize> Default for $name<CAP> {
821            /// Returns an empty string.
822            /// # Panics
823            /// Panics if `CAP > Self::MAX_CAPACITY`.
824            fn default() -> Self { Self::new() }
825        }
826        impl<const CAP: usize> ConstInit for $name<CAP> {
827            /// Returns an empty string.
828            /// # Panics
829            /// Panics if `CAP > Self::MAX_CAPACITY`.
830            const INIT: Self = Self::new();
831        }
832
833        impl<const CAP: usize> Display for $name<CAP> {
834            fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult<()> {
835                f.write_str(self.as_str())
836            }
837        }
838        impl<const CAP: usize> Debug for $name<CAP> {
839            fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult<()> {
840                write!(f, "{:?}", self.as_str())
841            }
842        }
843
844        /// Writes as much UTF-8-complete text as fits.
845        ///
846        /// If the input does not fit completely,
847        /// a prefix may have been written before returning [`FmtError`].
848        impl<const CAP: usize> FmtWrite for $name<CAP> {
849            fn write_str(&mut self, s: &str) -> FmtResult<()> {
850                self.try_push_str(s).map_err(|_| FmtError)?; // RETHINK using try_push_complete_str
851                Ok(())
852            }
853            fn write_char(&mut self, c: char) -> FmtResult<()> {
854                self.try_push(c).map_err(|_| FmtError)?;
855                Ok(())
856            }
857        }
858
859        impl<const CAP: usize> PartialEq for $name<CAP> {
860            fn eq(&self, other: &Self) -> bool { self.eq(&other) }
861        }
862
863        impl<const CAP: usize> PartialEq<str> for $name<CAP> { // str on the RHS
864            fn eq(&self, string: &str) -> bool { self.as_str() == string }
865        }
866        impl<const CAP: usize> PartialEq<&str> for $name<CAP> { // &str on the RHS
867            fn eq(&self, string: &&str) -> bool { self.as_str() == *string }
868        }
869        impl<const CAP: usize> PartialEq<&[u8]> for $name<CAP> { // &[u8] on the RHS
870            fn eq(&self, bytes: &&[u8]) -> bool { self.as_bytes() == *bytes }
871        }
872
873        impl<const CAP: usize> PartialEq<$name<CAP>> for str { // str on the LHS
874            fn eq(&self, string: &$name<CAP>) -> bool { self == string.as_str() }
875        }
876        impl<const CAP: usize> PartialEq<$name<CAP>> for &str { // &str on the LHS
877            fn eq(&self, string: &$name<CAP>) -> bool { *self == string.as_str() }
878        }
879        impl<const CAP: usize> PartialEq<$name<CAP>> for &[u8] { // &[u8] on the LHS
880            fn eq(&self, string: &$name<CAP>) -> bool { *self == string.as_bytes() }
881        }
882
883        impl<const CAP: usize> PartialOrd for $name<CAP> {
884            fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.cmp(other)) }
885        }
886        impl<const CAP: usize> Ord for $name<CAP> {
887            fn cmp(&self, other: &Self) -> Ordering { self.as_str().cmp(other.as_str()) }
888        }
889
890        impl<const CAP: usize> Hash for $name<CAP> {
891            fn hash<H: Hasher>(&self, state: &mut H) { self.as_str().hash(state); }
892        }
893
894        impl<const CAP: usize> Deref for $name<CAP> {
895            type Target = str;
896            fn deref(&self) -> &Self::Target { self.as_str() }
897        }
898
899        impl<const CAP: usize> AsRef<str> for $name<CAP> {
900            fn as_ref(&self) -> &str { self.as_str() }
901        }
902
903        impl<const CAP: usize> AsRef<[u8]> for $name<CAP> {
904            fn as_ref(&self) -> &[u8] { self.as_bytes() }
905        }
906
907        /* AddAssign */
908
909        impl<const CAP: usize> AddAssign<char> for $name<CAP> {
910            /// Appends the character if it fits.
911            fn add_assign(&mut self, rhs: char) {
912                let _ = self.push(rhs);
913            }
914        }
915        impl<const CAP: usize> AddAssign<&str> for $name<CAP> {
916            /// Appends text in place, keeping the fitted UTF-8 prefix.
917            fn add_assign(&mut self, rhs: &str) {
918                let _ = self.push_str(rhs);
919            }
920        }
921        impl<const CAP: usize, const RHS: usize> AddAssign<&$name<RHS>> for $name<CAP> {
922            /// Concatenates by appending what fits into `self`'s capacity.
923            fn add_assign(&mut self, rhs: &$name<RHS>) {
924                let _ = self.push_str(rhs.as_str());
925            }
926        }
927        impl<const CAP: usize, const RHS: usize> AddAssign<$name<RHS>> for $name<CAP> {
928            /// Concatenates by appending what fits into `self`'s capacity.
929            fn add_assign(&mut self, rhs: $name<RHS>) {
930                let _ = self.push_str(rhs.as_str());
931            }
932        }
933        /// Appends non-NUL text in place, keeping the fitted UTF-8 prefix.
934        impl<const CAP: usize, const RHS: usize> AddAssign<&StringNonul<RHS>> for StringU8<CAP> {
935            fn add_assign(&mut self, rhs: &StringNonul<RHS>) {
936                let _ = self.push_str(rhs.as_str());
937            }
938        }
939        /// Appends non-NUL text in place, keeping the fitted UTF-8 prefix.
940        impl<const CAP: usize, const RHS: usize> AddAssign<StringNonul<RHS>> for StringU8<CAP> {
941            fn add_assign(&mut self, rhs: StringNonul<RHS>) {
942                let _ = self.push_str(rhs.as_str());
943            }
944        }
945
946        /* conversions */
947
948        impl<const CAP: usize> TryFrom<&str> for $name<CAP> {
949            type Error = MismatchedCapacity;
950
951            /// Tries to create a new string from the given `string` slice.
952            ///
953            /// This is implemented via `Self::`[`from_str()`][Self::from_str].
954            /// # Errors
955            /// Returns [`MismatchedCapacity`] if `CAP > Self::MAX_CAPACITY`
956            /// or if `CAP < string.len()`.
957            fn try_from(string: &str) -> Result<Self, MismatchedCapacity> { Self::from_str(string) }
958        }
959
960        impl<const CAP: usize> TryFrom<&[u8]> for $name<CAP> {
961            type Error = InvalidText;
962
963            /// Tries to create a new string from the given slice of` bytes`.
964            ///
965            /// # Errors
966            /// Returns [`InvalidText::MismatchedCapacity`] if `CAP > Self::MAX_CAPACITY`
967            /// or if `CAP < bytes.len()`,
968            /// and [`InvalidText::Utf8`] if `bytes` are not valid UTF-8.
969            fn try_from(bytes: &[u8]) -> Result<Self, InvalidText> {
970                $crate::unwrap![ok? Self::_check_cap_invalid_text()];
971                let bytes_len = bytes.len();
972                // CHECK MAYBE use this in more places? and made a helper fn?
973                if CAP < bytes_len { Err(MismatchedCapacity::too_small(CAP, bytes_len).into()) }
974                else {
975                    match Str::from_utf8(bytes) {
976                        Ok(_) => {
977                            let mut arr = [0; CAP];
978                            arr[..bytes_len].copy_from_slice(bytes);
979                            Ok(Self { arr, len: Self::_ni_usize(bytes_len) })
980                        },
981                        Err(e) => Err(e.into()),
982                    }
983                }
984            }
985        }
986
987        /* Extend & FromIterator */
988
989        impl<const CAP: usize> Extend<char> for $name<CAP> {
990            /// Creates an instance from an iterator of characters.
991            ///
992            /// Processes characters until it can fit no more, discarding the rest.
993            ///
994            /// # Panics
995            /// Panics if `CAP > Self::MAX_CAPACITY`.
996            ///
997            /// # Examples
998            /// ```
999            /// # use devela::StringU8;
1000            /// let chars = ['a', 'b', 'c', '€', 'さ'];
1001            /// let mut s = StringU8::<6>::new();
1002            /// s.extend(chars);
1003            /// assert_eq![s, "abc€"];
1004            /// ```
1005            fn extend<I: IntoIterator<Item=char>>(&mut self, iter: I) {
1006                for i in iter { is![self.push(i) == 0, break]; }
1007            }
1008        }
1009        impl<const CAP: usize> FromIterator<char> for $name<CAP> {
1010            /// Creates an instance from an iterator of characters.
1011            ///
1012            /// Processes characters until it can fit no more, discarding the rest.
1013            ///
1014            /// # Panics
1015            /// Panics if `CAP > Self::MAX_CAPACITY`.
1016            ///
1017            /// # Examples
1018            /// ```
1019            /// # use devela::StringU8;
1020            /// let chars = ['a', 'b', 'c', '€', 'さ'];
1021            /// assert_eq!(StringU8::<9>::from_iter(chars), "abc€さ");
1022            /// assert_eq!(StringU8::<6>::from_iter(chars), "abc€");
1023            /// assert_eq!(StringU8::<5>::from_iter(chars), "abc");
1024            /// assert_eq!(StringU8::<2>::from_iter(chars), "ab");
1025            /// assert_eq!(StringU8::<0>::from_iter(chars), "");
1026            /// ```
1027            fn from_iter<I: IntoIterator<Item=char>>(iter: I) -> Self {
1028                let mut string = $name::new();
1029                string.extend(iter);
1030                string
1031            }
1032        }
1033    };
1034}
1035impl_str_u!();
1036
1037#[cfg(test)]
1038mod tests {
1039    #[allow(unused_imports)]
1040    use super::*;
1041
1042    #[test]
1043    fn niche_size() {
1044        assert_eq!(size_of::<StringU8::<0>>(), 1);
1045        assert_eq!(size_of::<Option<StringU8::<0>>>(), 1);
1046        assert_eq!(size_of::<StringU8::<3>>(), 4);
1047        assert_eq!(size_of::<Option<StringU8::<3>>>(), 4);
1048        assert_eq!(size_of::<StringU8::<254>>(), 255);
1049        assert_eq!(size_of::<Option<StringU8::<254>>>(), 255);
1050    }
1051    #[test]
1052    fn capacity_boundary() {
1053        assert!(StringU8::<254>::new_checked().is_ok());
1054        assert!(StringU8::<255>::new_checked().is_err());
1055        assert!(StringU8::<256>::new_checked().is_err());
1056    }
1057    #[test]
1058    #[should_panic]
1059    fn new_panics_at_forbidden_capacity() {
1060        let _ = StringU8::<255>::new();
1061    }
1062    #[test]
1063    fn max_valid_len_is_254_for_u8() {
1064        let s = StringU8::<254>::from_str(&"a".repeat(254)).unwrap();
1065        assert_eq!(s.len(), 254);
1066        assert!(s.is_full());
1067        assert_eq!(s.remaining_capacity(), 0);
1068    }
1069    #[test]
1070    fn rejects_len_255_for_u8() {
1071        let text = "a".repeat(255);
1072        assert!(StringU8::<254>::from_str(&text).is_err());
1073    }
1074    #[test]
1075    fn from_array_max_valid_capacity() {
1076        let bytes = [b'a'; 254];
1077        let s = StringU8::<254>::from_array(bytes).unwrap();
1078        assert_eq!(s.len(), 254);
1079        assert!(s.is_full());
1080        assert![StringU8::<255>::from_array([b'a'; 255]).is_err()];
1081    }
1082    #[test]
1083    fn nleft_max_valid_capacity() {
1084        let s = StringU8::<254>::from_array_nleft([b'a'; 254], u8::MAX).unwrap();
1085        assert_eq!(s.len(), 254);
1086        assert![StringU8::<255>::from_array_nleft([b'a'; 255], 1).is_err()];
1087    }
1088    #[test]
1089    fn nright_rejects_split_utf8_suffix() {
1090        let bytes = [0x82, 0xAC]; // continuation bytes, invalid as standalone UTF-8
1091        assert!(StringU8::<2>::from_array_nright(bytes, 2).is_err());
1092    }
1093    #[test]
1094    #[should_panic]
1095    fn trusted_len_must_not_exceed_cap() {
1096        let _ = StringU8::<4>::_from_array_len_trusted([0; 4], 5);
1097    }
1098    #[test]
1099    fn push() {
1100        let mut s = StringU8::<3>::new();
1101        assert![s.try_push('ñ').is_ok()];
1102        assert_eq![2, s.len()];
1103        assert![s.try_push('ñ').is_err()];
1104        assert_eq![2, s.len()];
1105        assert![s.try_push('a').is_ok()];
1106        assert_eq![3, s.len()];
1107    }
1108    #[test]
1109    fn push_str() {
1110        let mut s = StringU8::<5>::new();
1111        assert_eq!(s.push_str("café"), 5);
1112
1113        let mut s = StringU8::<4>::new();
1114        assert_eq!(s.push_str("café"), 3);
1115        assert_eq!(s, "caf")
1116    }
1117    #[test]
1118    fn try_push_str() {
1119        let mut s = StringU8::<5>::new();
1120        assert_eq!(s.try_push_str("café"), Ok(5));
1121
1122        let mut s = StringU8::<4>::new();
1123        assert_eq!(s.try_push_str("café"), Err(3));
1124        assert_eq!(s, "caf")
1125    }
1126    #[test]
1127    fn try_push_str_complete() {
1128        let mut s = StringU8::<5>::new();
1129        assert_eq!(s.try_push_str_complete("café"), Ok(5));
1130
1131        let mut s = StringU8::<4>::new();
1132        assert_eq!(s.try_push_str_complete("café"), Err(0));
1133        assert_eq!(s, "")
1134    }
1135    #[test]
1136    fn push_str_never_splits_utf8() {
1137        let mut s = StringU8::<4>::new();
1138        assert_eq!(s.push_str("€€"), 3);
1139        assert_eq!(s.as_str(), "€");
1140        assert_eq!(s.len(), 3);
1141    }
1142    #[test]
1143    fn try_push_str_complete_is_atomic() {
1144        let mut s = StringU8::<4>::from_str("ab").unwrap();
1145        assert_eq!(s.try_push_str_complete("€"), Err(0));
1146        assert_eq!(s.as_str(), "ab");
1147        assert_eq!(s.len(), 2);
1148    }
1149    #[test]
1150    fn push_charu_appends() {
1151        let mut s = StringU8::<5>::from_str("a").unwrap();
1152        assert_eq!(s.push_charu(charu::from_char('€')), 3);
1153        assert_eq!(s.as_str(), "a€");
1154        assert_eq!(s.len(), 4);
1155    }
1156    #[test]
1157    fn pop() {
1158        let mut s = StringU8::<3>::new();
1159        s.push('ñ');
1160        s.push('a');
1161        assert_eq![Some('a'), s.pop()];
1162        assert_eq![Some('ñ'), s.pop()];
1163        assert_eq![None, s.pop()];
1164    }
1165    #[test]
1166    fn ord_ignores_unused_bytes() {
1167        let a = StringU8::<2>::from_array_nleft([b'a', 1], 1).unwrap();
1168        let b = StringU8::<2>::from_array_nleft([b'a', 2], 1).unwrap();
1169        assert_eq!(a, b);
1170        assert_eq!(a.cmp(&b), core::cmp::Ordering::Equal);
1171    }
1172}