devela/text/str/
string_u.rs

1// devela::text::string_u
2//
3//! `String` backed by an array.
4//
5// TOC
6// - impl_str_u!
7//   - definitions
8//   - trait impls
9// - tests
10
11#[cfg(feature = "str")]
12use crate::Str;
13use crate::{
14    _core::fmt, ConstDefault, Deref, InvalidText, InvalidUtf8, IterChars, Mismatch,
15    MismatchedCapacity, NotEnoughElements, is, paste, text::char::*, unwrap,
16};
17#[cfg(all(_str_u··, feature = "alloc"))]
18use crate::{CString, ToString};
19#[allow(unused, reason = "±unsafe | ±_cmp*")]
20use crate::{Compare, cfor};
21
22macro_rules! impl_str_u {
23    () => {
24        impl_str_u![
25            u8:"_str_u8":"_cmp_u8",
26            u16:"_str_u16":"_cmp_u16",
27            u32:"_str_u32":"_cmp_u32",
28            usize:"_str_usize"
29        ];
30    };
31
32    (
33    // $t:    the length type. E.g.: u8.
34    // $cap:  the capability that enables the implementation. E.g. _str_u8.
35    // $cmp:  the optional capability associated to optional const methods. E.g. _cmp_u8.
36    //
37    // $name: the name of the type. E.g.: StringU8.
38    $( $t:ty : $cap:literal $(: $cmp:literal)? ),+) => {
39        $(
40            #[cfg(feature = $cap)]
41            paste! { impl_str_u![@[<String $t:camel>], $t:$cap $(:$cmp)? ]; }
42        )+
43    };
44
45    (@$name:ty, $t:ty : $cap:literal $(: $cmp:literal)? ) => { paste! {
46        /* definitions */
47
48        #[doc = crate::TAG_TEXT!()]
49        #[doc = "A UTF-8–encoded string, backed by an array with [`" $t "::MAX`] bytes of capacity."]
50        ///
51        #[doc = "Internally, the current length is stored as a [`" $t "`]."]
52        ///
53        /// # Features
54        /// It will be implemented if the corresponding feature is enabled:
55        /// `_str_u[8|16|32|size]`.
56        ///
57        /// ## Methods
58        /// - Construct:
59        ///   [`new`][Self::new],
60        ///   [`from_char`][Self::from_char]*(
61        ///     [`7`](Self::from_char7),
62        ///     [`8`](Self::from_char8),
63        ///     [`16`](Self::from_char16).
64        ///   )*.
65        /// - Deconstruct:
66        ///   [`into_array`][Self::into_array],
67        ///   [`as_array`][Self::as_array],
68        ///   [`as_bytes`][Self::as_bytes]
69        ///     *([mut][Self::as_bytes_mut]<sup title="unsafe function">⚠</sup>)*,
70        ///   [`as_str`][Self::as_str]
71        ///     *([mut][Self::as_mut_str]<sup title="unsafe function">⚠</sup>)*,
72        ///   [`chars`][Self::chars],
73        ///   [`to_cstring`][Self::to_cstring](`alloc`).
74        /// - Query:
75        ///   [`len`][Self::len],
76        ///   [`is_empty`][Self::is_empty],
77        ///   [`is_full`][Self::is_full],
78        ///   [`capacity`][Self::capacity],
79        ///   [`remaining_capacity`][Self::remaining_capacity].
80        /// - Operations:
81        ///   [`clear`][Self::clear], [`reset`][Self::reset],
82        ///   [`pop`][Self::pop]*([try][Self::try_pop])*,
83        ///   [`push`][Self::push]*([try][Self::try_push])*.
84        ///   [`push_str`][Self::push]*([try][Self::try_push_str])*,
85        ///   [`try_push_str_complete`][Self::try_push_str_complete].
86        #[must_use]
87        #[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
88        #[cfg_attr(nightly_doc, doc(cfg(feature = $cap)))]
89        pub struct $name<const CAP: usize> {
90            // WAIT: for when we can use CAP: u8 for panic-less const boundary check.
91            arr: [u8; CAP],
92            len: $t,
93        }
94
95        impl<const CAP: usize> $name<CAP> {
96            /* construct */
97
98            #[doc = "Creates a new empty `String" $t:camel "` with a capacity of `CAP` bytes."]
99            ///
100            /// # Errors
101            #[doc = "Returns [`MismatchedCapacity`] if `CAP > `[`" $t "::MAX`]."]
102            pub const fn new() -> Result<Self, MismatchedCapacity> {
103                if CAP <= $t::MAX as usize {
104                    Ok(Self { arr: [0; CAP], len: 0 })
105                } else {
106                    Err(MismatchedCapacity::closed(0, <$t>::MAX as usize, CAP))
107                }
108            }
109
110            /* query */
111
112            /// Returns the current string length in bytes.
113            #[must_use] #[rustfmt::skip]
114            pub const fn len(&self) -> usize { self.len as usize }
115
116            /// Returns `true` if the current length is 0.
117            #[must_use] #[rustfmt::skip]
118            pub const fn is_empty(&self) -> bool { self.len == 0 }
119
120            /// Returns `true` if the current remaining capacity is 0.
121            #[must_use] #[rustfmt::skip]
122            pub const fn is_full(&self) -> bool { self.len == CAP as $t }
123
124            /// Returns the total capacity in bytes.
125            #[must_use] #[rustfmt::skip]
126            pub const fn capacity() -> usize { CAP }
127
128            /// Returns the remaining capacity in bytes.
129            #[must_use] #[rustfmt::skip]
130            pub const fn remaining_capacity(&self) -> usize { CAP - self.len as usize }
131
132            /* deconstruct */
133
134            /// Returns the inner array with the full contents.
135            ///
136            /// The array contains all the bytes, including those outside the current length.
137            #[must_use] #[rustfmt::skip]
138            pub const fn into_array(self) -> [u8; CAP] { self.arr }
139
140            /// Returns a copy of the inner array with the full contents.
141            ///
142            /// The array contains all the bytes, including those outside the current length.
143            #[must_use] #[rustfmt::skip]
144            pub const fn as_array(&self) -> [u8; CAP] { self.arr }
145
146            /// Returns a byte slice of the inner string slice.
147            // WAIT: [split_at_unchecked](https://github.com/rust-lang/rust/issues/76014)
148            #[must_use] #[rustfmt::skip]
149            pub const fn as_bytes(&self) -> &[u8] { self.arr.split_at(self.len as usize).0 }
150
151            /// Returns an exclusive byte slice of the inner string slice.
152            ///
153            /// # Safety
154            /// The caller must ensure that the content of the slice is valid UTF-8
155            /// before the borrow ends and the underlying `str` is used.
156            ///
157            /// Use of a `str` whose contents are not valid UTF-8 is undefined behavior.
158            #[must_use]
159            #[cfg(all(not(feature = "safe_text"), feature = "unsafe_slice"))]
160            #[cfg_attr(nightly_doc, doc(cfg(feature = "unsafe_slice")))]
161            pub unsafe fn as_bytes_mut(&mut self) -> &mut [u8] {
162                // SAFETY: caller must ensure safety
163                unsafe { self.arr.get_unchecked_mut(0..self.len as usize) }
164            }
165
166            /// Returns the inner string slice.
167            ///
168            /// # Features
169            /// Makes use of the `unsafe_str` feature if enabled.
170            #[must_use]
171            pub const fn as_str(&self) -> &str {
172                #[cfg(any(feature = "safe_text", not(feature = "unsafe_str")))]
173                return unwrap![ok_expect Str::from_utf8(self.as_bytes()), "Invalid UTF-8"];
174
175                #[cfg(all(not(feature = "safe_text"), feature = "unsafe_str"))]
176                // SAFETY: we ensure to contain only valid UTF-8
177                unsafe { Str::from_utf8_unchecked(self.as_bytes()) }
178            }
179
180            /// Returns the exclusive inner string slice.
181            /// Makes use of the `unsafe_str` feature if enabled.
182            #[must_use]
183            #[cfg(all(not(feature = "safe_text"), feature = "unsafe_slice"))]
184            #[cfg_attr(nightly_doc, doc(cfg(feature = "unsafe_slice")))]
185            pub fn as_mut_str(&mut self) -> &mut str {
186                unsafe { &mut *(self.as_bytes_mut() as *mut [u8] as *mut str) }
187            }
188
189            /// Returns an iterator over the `chars` of this grapheme cluster.
190            #[rustfmt::skip]
191            pub fn chars(&self) -> IterChars<'_> { self.as_str().chars() }
192
193            /// Returns a new allocated C-compatible, nul-terminanted string.
194            #[must_use] #[rustfmt::skip]
195            #[cfg(feature = "alloc")]
196            #[cfg_attr(nightly_doc, doc(cfg(feature = "alloc")))]
197            pub fn to_cstring(&self) -> CString { CString::new(self.to_string()).unwrap() }
198
199            /* operations */
200
201            /// Sets the length to 0.
202            pub fn clear(&mut self) {
203                self.len = 0;
204            }
205
206            /// Sets the length to 0, and resets all the bytes to 0.
207            pub fn reset(&mut self) {
208                self.arr = [0; CAP];
209                self.len = 0;
210            }
211
212            /// Removes the last character and returns it, or `None` if
213            /// the string is empty.
214            #[must_use] #[rustfmt::skip]
215            pub fn pop(&mut self) -> Option<char> {
216                self.as_str().chars().last().map(|c| { self.len -= c.len_utf8() as $t; c })
217            }
218
219            /// Tries to remove the last character and returns it, or `None` if
220            /// the string is empty.
221            ///
222            /// # Errors
223            /// Returns a [`NotEnoughElements`] error
224            /// if the capacity is not enough to hold the `character`.
225            pub fn try_pop(&mut self) -> Result<char, NotEnoughElements> {
226                self.as_str().chars().last().map(|c| {
227                    self.len -= c.len_utf8() as $t; c
228                })
229                .ok_or(NotEnoughElements(Some(1)))
230            }
231
232            /// Appends to the end of the string the given `character`.
233            ///
234            /// Returns the number of bytes written.
235            ///
236            /// It will return 0 bytes if the given `character` doesn't fit in
237            /// the remaining capacity.
238            pub fn push(&mut self, character: char) -> usize {
239                let char_len = character.len_utf8();
240                if self.remaining_capacity() >= char_len {
241                    let beg = self.len as usize;
242                    let end = beg + char_len;
243                    let _ = character.encode_utf8(&mut self.arr[beg..end]);
244                    self.len += char_len as $t;
245                    char_len
246                } else {
247                    0
248                }
249            }
250
251            /// Tries to append to the end of the string the given `character`.
252            ///
253            /// Returns the number of bytes written.
254            ///
255            /// # Errors
256            /// Returns a [`MismatchedCapacity`] error
257            /// if the capacity is not enough to hold the `character`.
258            pub fn try_push(&mut self, character: char) -> Result<usize, MismatchedCapacity> {
259                let char_len = character.len_utf8();
260                if self.remaining_capacity() >= char_len {
261                    let beg = self.len as usize;
262                    let end = beg + char_len;
263                    let _ = character.encode_utf8(&mut self.arr[beg..end]);
264                    self.len += char_len as $t;
265                    Ok(char_len)
266                } else {
267                    Err(MismatchedCapacity::closed(0, self.len() + character.len_utf8(), CAP))
268                }
269            }
270
271            /// Appends to the end the fitting characters from the given `string` slice.
272            ///
273            /// Nul characters will be stripped out.
274            ///
275            /// Returns the number of bytes written, which will be 0
276            /// if not even the first non-nul character can fit.
277            pub fn push_str(&mut self, string: &str) -> usize {
278                let mut bytes_written = 0;
279                for character in string.chars() {
280                    let char_len = character.len_utf8();
281                    if self.len as usize + char_len <= CAP {
282                        let start_pos = self.len as usize;
283                        character.encode_utf8(&mut self.arr[start_pos..]);
284                        self.len += char_len as $t;
285                        bytes_written += char_len;
286                    } else {
287                        break;
288                    }
289                }
290                bytes_written
291            }
292
293            /// Tries to append to the end the characters from the given `string` slice.
294            ///
295            /// Returns the number of bytes written.
296            ///
297            /// # Errors
298            /// Returns [`MismatchedCapacity`] if the capacity is not enough
299            /// to hold even the first character.
300            pub fn try_push_str(&mut self, string: &str) -> Result<usize, MismatchedCapacity> {
301                is![string.is_empty(); return Ok(0)];
302                let first_char_len = string.chars().next().unwrap().len_utf8();
303                if self.remaining_capacity() < first_char_len {
304                    Err(MismatchedCapacity::closed(0, self.len() + first_char_len, CAP))
305                } else {
306                    Ok(self.push_str(string))
307                }
308            }
309
310            /// Tries to append the complete `string` slice to the end.
311            ///
312            /// Returns the number of bytes written in success.
313            ///
314            /// # Errors
315            /// Returns [`MismatchedCapacity`] if the slice wont completely fit.
316            pub fn try_push_str_complete(&mut self, string: &str)
317            -> Result<usize, MismatchedCapacity> {
318                if self.remaining_capacity() >= string.len() {
319                    Ok(self.push_str(string))
320                } else {
321                    Err(MismatchedCapacity::closed(0, self.len() + string.len(), CAP))
322                }
323            }
324
325            /* from char  */
326
327            #[doc = "Creates a new `String" $t:camel "` from a `char`."]
328            ///
329            /// # Errors
330            #[doc = "Returns [`MismatchedCapacity`] if `CAP > `[`" $t "::MAX`]."]
331            /// or if `CAP < c.`[`len_utf8()`][crate::UnicodeScalar#method.len_utf8].
332            ///
333            #[doc = "It will always succeed if `CAP >= 4 && CAP <= `[`" $t "::MAX`]."]
334            #[rustfmt::skip]
335            pub const fn from_char(c: char) -> Result<Self, MismatchedCapacity> {
336                let mut new = unwrap![ok? Self::new()];
337                let bytes = Char::to_utf8_bytes(c);
338                new.len = Char::utf8_len(bytes[0]) as $t;
339                new.arr[0] = bytes[0];
340                if new.len > 1 { new.arr[1] = bytes[1]; }
341                if new.len > 2 { new.arr[2] = bytes[2]; }
342                if new.len > 3 { new.arr[3] = bytes[3]; }
343                Ok(new)
344            }
345
346            #[doc = "Creates a new `String" $t:camel "` from a `char7`."]
347            ///
348            /// # Errors
349            #[doc = "Returns [`MismatchedCapacity`] if `CAP > `[`" $t "::MAX`]."]
350            /// or if `CAP < 1.
351            ///
352            #[doc = "It will always succeed if `CAP >= 1 && CAP <= `[`" $t "::MAX`]."]
353            #[cfg(feature = "_char7")]
354            #[cfg_attr(nightly_doc, doc(cfg(feature = "_char7")))]
355            pub const fn from_char7(c: char7) -> Result<Self, MismatchedCapacity> {
356                let mut new = unwrap![ok? Self::new()];
357                new.arr[0] = c.to_utf8_bytes()[0];
358                new.len = 1;
359                Ok(new)
360            }
361
362            #[doc = "Creates a new `String" $t:camel "` from a `char8`."]
363            ///
364            /// # Errors
365            #[doc = "Returns [`MismatchedCapacity`] if `CAP > `[`" $t "::MAX`]."]
366            /// or if `CAP < 2.
367            ///
368            #[doc = "It will always succeed if `CAP >= 2 && CAP <= `[`" $t "::MAX`]."]
369            #[rustfmt::skip]
370            #[cfg(feature = "_char8")]
371            #[cfg_attr(nightly_doc, doc(cfg(feature = "_char8")))]
372            pub const fn from_char8(c: char8) -> Result<Self, MismatchedCapacity> {
373                let mut new = unwrap![ok? Self::new()];
374                let bytes = c.to_utf8_bytes();
375                new.len = Char::utf8_len(bytes[0]) as $t;
376                new.arr[0] = bytes[0];
377                if new.len > 1 { new.arr[1] = bytes[1]; }
378                Ok(new)
379            }
380
381            #[doc = "Creates a new `String" $t:camel "` from a `char16`."]
382            ///
383            /// # Errors
384            #[doc = "Returns [`MismatchedCapacity`] if `CAP > `[`" $t
385                "::MAX`]` || CAP < c.`[`len_utf8()`][char16#method.len_utf8]."]
386            ///
387            #[doc = "It will always succeed if `CAP >= 3 && CAP <= `[`" $t "::MAX`]."]
388            #[rustfmt::skip]
389            #[cfg(feature = "_char16")]
390            #[cfg_attr(nightly_doc, doc(cfg(feature = "_char16")))]
391            pub const fn from_char16(c: char16) -> Result<Self, MismatchedCapacity> {
392                let mut new = unwrap![ok? Self::new()];
393                let bytes = c.to_utf8_bytes();
394                new.len = Char::utf8_len(bytes[0]) as $t;
395                new.arr[0] = bytes[0];
396                if new.len > 1 { new.arr[1] = bytes[1]; }
397                if new.len > 2 { new.arr[2] = bytes[2]; }
398                Ok(new)
399            }
400
401            /* from bytes */
402
403            /// Returns a string from a slice of `bytes`.
404            ///
405            /// # Errors
406            /// Returns [`InvalidUtf8`] if the bytes are not valid UTF-8.
407            pub const fn from_bytes(bytes: [u8; CAP]) -> Result<Self, InvalidUtf8> {
408                match Str::from_utf8(&bytes) {
409                    Ok(_) => {
410                        Ok(Self { arr: bytes, len: CAP as $t })
411                    },
412                    Err(e) => Err(e),
413                }
414            }
415
416            /// Returns a string from an array of `bytes` that must be valid UTF-8.
417            ///
418            /// # Safety
419            /// The caller must ensure that the content of the slice is valid UTF-8.
420            ///
421            /// Use of a `str` whose contents are not valid UTF-8 is undefined behavior.
422            #[cfg(all(not(feature = "safe_text"), feature = "unsafe_str"))]
423            #[cfg_attr(nightly_doc, doc(cfg(feature = "unsafe_str")))]
424            pub const unsafe fn from_bytes_unchecked(bytes: [u8; CAP]) -> Self {
425                Self { arr: bytes, len: CAP as $t }
426            }
427
428            /// Returns a string from an array of `bytes`,
429            /// truncated to `n` bytes counting from the left.
430            ///
431            /// The new `length` is maxed out at `CAP`.
432            ///
433            /// # Errors
434            /// Returns [`InvalidUtf8`] if the bytes are not valid UTF-8.
435            $(
436            /// # Features
437            #[doc = "This method will only be *const* if the `" $cmp "` feature is enabled."]
438            #[cfg(feature = $cmp)]
439            )? // $cmp
440            pub const fn from_bytes_nleft(bytes: [u8; CAP], length: $t)
441            -> Result<Self, InvalidUtf8> {
442                let length = Compare(length).min(CAP as $t);
443                match Str::from_utf8(bytes.split_at(length as usize).0) {
444                    Ok(_) => Ok(Self { arr: bytes, len: length }),
445                    Err(e) => Err(e),
446                }
447            }
448            $( // $cmp
449            #[allow(missing_docs)]
450            #[cfg(not(feature = $cmp))]
451            pub fn from_bytes_nleft(bytes: [u8; CAP], length: $t) -> Result<Self, InvalidUtf8> {
452                let length = length.min(CAP as $t);
453                match Str::from_utf8(bytes.split_at(length as usize).0) {
454                    Ok(_) => Ok(Self { arr: bytes, len: length }),
455                    Err(e) => Err(e),
456                }
457            }
458            )?
459
460            /// Returns a string from an array of `bytes`,
461            /// truncated to `n` bytes counting from the left,
462            /// which must be valid UTF-8.
463            ///
464            /// The new `length` is maxed out at `CAP`.
465            ///
466            /// # Safety
467            /// The caller must ensure that the content of the truncated slice is valid UTF-8.
468            ///
469            /// Use of a `str` whose contents are not valid UTF-8 is undefined behavior.
470            $(
471            /// # Features
472            #[doc = "This method will only be *const* if the `" $cmp "` feature is enabled."]
473            #[cfg(feature = $cmp)]
474            )? // $cmp
475            #[cfg(all(not(feature = "safe_text"), feature = "unsafe_str"))]
476            #[cfg_attr(nightly_doc, doc(cfg(feature = "unsafe_str")))]
477            pub const unsafe fn from_bytes_nleft_unchecked(bytes: [u8; CAP], length: $t) -> Self {
478                Self { arr: bytes, len: Compare(length).min(CAP as $t) }
479            }
480            $( // $cmp
481            #[allow(missing_docs, clippy::missing_safety_doc)]
482            #[cfg(not(feature = $cmp))]
483            #[cfg(all(not(feature = "safe_text"), feature = "unsafe_str"))]
484            pub unsafe fn from_bytes_nleft_unchecked(bytes: [u8; CAP], length: $t) -> Self {
485                Self { arr: bytes, len: length.min(CAP as $t) }
486            }
487            )?
488
489            /// Returns a string from an array of `bytes`,
490            /// truncated to `n` bytes counting from the right.
491            ///
492            /// The new `length` is maxed out at `CAP`.
493            /// Bytes are shift-copied without allocating a new array.
494            ///
495            /// # Errors
496            /// Returns [`InvalidUtf8`] if the bytes are not valid UTF-8.
497            ///
498            $(
499            /// # Features
500            #[doc = "This method will only be *const* if the `" $cmp "` feature is enabled."]
501            #[cfg(feature = $cmp)]
502            )? // $cmp
503            pub const fn from_bytes_nright(mut bytes: [u8; CAP], length: $t)
504            -> Result<Self, InvalidUtf8> {
505                let length = Compare(length).min(CAP as $t);
506                let ulen = length as usize;
507                let start = CAP - ulen;
508                cfor![i in 0..ulen => {
509                    bytes[i] = bytes[start + i];
510                }];
511                match Str::from_utf8(bytes.split_at(ulen).0) {
512                    Ok(_) => Ok(Self { arr: bytes, len: length }),
513                    Err(e) => Err(e),
514                }
515            }
516            $( // $cmp
517            #[allow(missing_docs)]
518            #[cfg(not(feature = $cmp))]
519            pub fn from_bytes_nright(mut bytes: [u8; CAP], length: $t)
520            -> Result<Self, InvalidUtf8> {
521                let length = length.min(CAP as $t);
522                let ulen = length as usize;
523                let start = CAP - ulen;
524                for i in 0..ulen {
525                    bytes[i] = bytes[start + i];
526                }
527                match Str::from_utf8(bytes.split_at(ulen).0) {
528                    Ok(_) => Ok(Self { arr: bytes, len: length }),
529                    Err(e) => Err(e),
530                }
531            }
532            )?
533
534            /// Returns a string from an array of `bytes`,
535            /// truncated to `n` bytes counting from the right,
536            /// which must be valid UTF-8.
537            ///
538            /// The new `length` is maxed out at `CAP`.
539            /// Bytes are shift-copied without allocating a new array.
540            ///
541            /// # Safety
542            /// The caller must ensure that the content of the truncated slice is valid UTF-8.
543            ///
544            /// Use of a `str` whose contents are not valid UTF-8 is undefined behavior.
545            $(
546            /// # Features
547            #[doc = "This method will only be *const* if the `" $cmp "` feature is enabled."]
548            #[cfg(feature = $cmp)]
549            )? // $cmp
550            #[cfg(all(not(feature = "safe_text"), feature = "unsafe_str"))]
551            #[cfg_attr(nightly_doc, doc(cfg(feature = "unsafe_str")))]
552            pub const unsafe fn from_bytes_nright_unchecked(mut bytes: [u8; CAP], length: $t)
553                -> Self {
554                let length = Compare(length).min(CAP as $t);
555                let ulen = length as usize;
556                let start = CAP - ulen;
557                cfor![i in 0..ulen => {
558                    bytes[i] = bytes[start + i];
559                }];
560                Self { arr: bytes, len: length }
561            }
562            $( // $cmp
563            #[allow(missing_docs, clippy::missing_safety_doc)]
564            #[cfg(not(feature = $cmp))]
565            #[cfg(all(not(feature = "safe_text"), feature = "unsafe_str"))]
566            pub unsafe fn from_bytes_nright_unchecked(mut bytes: [u8; CAP], length: $t)
567                -> Self {
568                let length = length.min(CAP as $t);
569                let ulen = length as usize;
570                let start = CAP - ulen;
571                for i in 0..ulen {
572                    bytes[i] = bytes[start + i];
573                }
574                Self { arr: bytes, len: length }
575            }
576            )?
577        }
578
579        /* traits implementations */
580
581        impl<const CAP: usize> Default for $name<CAP> {
582            /// Returns an empty string.
583            ///
584            /// # Panics
585            #[doc = "Panics if `CAP > `[`" $t "::MAX`]."]
586            #[rustfmt::skip]
587            fn default() -> Self { Self::new().unwrap() }
588        }
589        impl<const CAP: usize> ConstDefault for $name<CAP> {
590            /// Returns an empty string.
591            ///
592            /// # Panics
593            #[doc = "Panics if `CAP > `[`" $t "::MAX`]."]
594            const DEFAULT: Self = unwrap![ok Self::new()];
595        }
596
597        impl<const CAP: usize> fmt::Display for $name<CAP> {
598            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
599                write!(f, "{}", self.as_str())
600            }
601        }
602
603        impl<const CAP: usize> fmt::Debug for $name<CAP> {
604            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
605                write!(f, "{:?}", self.as_str())
606            }
607        }
608
609        impl<const CAP: usize> PartialEq<&str> for $name<CAP> {
610            #[rustfmt::skip]
611            fn eq(&self, slice: &&str) -> bool { self.as_str() == *slice }
612        }
613        // and for when &str is on the left-hand side of the comparison
614        impl<const CAP: usize> PartialEq<$name<CAP>> for &str {
615            #[rustfmt::skip]
616            fn eq(&self, string: & $name<CAP>) -> bool { *self == string.as_str() }
617        }
618
619        impl<const CAP: usize> Deref for $name<CAP> {
620            type Target = str;
621            #[rustfmt::skip]
622            fn deref(&self) -> &Self::Target { self.as_str() }
623        }
624
625        impl<const CAP: usize> AsRef<str> for $name<CAP> {
626            #[rustfmt::skip]
627            fn as_ref(&self) -> &str { self.as_str() }
628        }
629
630        impl<const CAP: usize> AsRef<[u8]> for $name<CAP> {
631            #[rustfmt::skip]
632            fn as_ref(&self) -> &[u8] { self.as_bytes() }
633        }
634
635        impl<const CAP: usize> TryFrom<&str> for $name<CAP> {
636            type Error = MismatchedCapacity;
637
638            #[doc = "Tries to create a new `String" $t:camel "` from the given `string` slice."]
639            ///
640            /// # Errors
641            #[doc = "Returns [`MismatchedCapacity`] if `CAP > `[`" $t "::MAX`]"]
642            /// or if `CAP < string.len()`.
643            fn try_from(string: &str) -> Result<Self, MismatchedCapacity> {
644                if CAP < string.len() {
645                    Err(MismatchedCapacity::closed(0, CAP + string.len(), CAP))
646                } else {
647                    let mut new_string = Self::new()?;
648                    let bytes = string.as_bytes();
649                    new_string.arr[..bytes.len()].copy_from_slice(bytes);
650                    Ok(new_string)
651                }
652            }
653        }
654
655        impl<const CAP: usize> TryFrom<&[u8]> for $name<CAP> {
656            type Error = InvalidText;
657
658            #[doc = "Tries to create a new `String" $t:camel "` from the given slice of` bytes`."]
659            ///
660            /// # Errors
661            #[doc = "Returns [`InvalidText::Capacity`] if `CAP > `[`" $t "::MAX`], or if "]
662            /// `CAP < bytes.len()`, and [`InvalidText::Utf8`] if the `bytes` are not valid UTF-8.
663            fn try_from(bytes: &[u8]) -> Result<Self, InvalidText> {
664                if CAP < bytes.len() {
665                    return Err(InvalidText::Capacity(Mismatch::in_closed_interval(
666                        0,
667                        bytes.len(),
668                        CAP,
669                        "",
670                    )));
671                } else {
672                    match Str::from_utf8(bytes) {
673                        Ok(_) => {
674                            let mut arr = [0; CAP];
675                            arr[..bytes.len()].copy_from_slice(bytes);
676                            Ok(Self { arr, len: bytes.len() as $t })
677                        },
678                        Err(e) => Err(e.into()),
679                    }
680                }
681            }
682        }
683
684        #[cfg(all(feature = "std", any(unix, target_os = "wasi")))]
685        mod [< std_impls_ $t >] {
686            use super::$name;
687            use std::ffi::OsStr;
688
689            #[cfg(unix)]
690            use std::os::unix::ffi::OsStrExt;
691            #[cfg(target_os = "wasi")]
692            use std::os::wasi::ffi::OsStrExt;
693
694            #[cfg_attr(nightly_doc, doc(cfg(
695                all(feature = "std", any(unix, target_os = "wasi"))
696            )))]
697            impl<const CAP: usize> AsRef<OsStr> for $name<CAP> {
698                fn as_ref(&self) -> &OsStr {
699                    OsStr::from_bytes(self.as_bytes())
700                }
701            }
702        }
703    }};
704}
705impl_str_u!();
706
707#[cfg(test)]
708mod tests {
709    #[allow(unused_imports)]
710    use super::*;
711
712    #[test]
713    #[cfg(feature = "_str_u8")]
714    fn push() {
715        let mut s = StringU8::<3>::new().unwrap();
716        assert![s.try_push('ñ').is_ok()];
717        assert_eq![2, s.len()];
718        assert![s.try_push('ñ').is_err()];
719        assert_eq![2, s.len()];
720        assert![s.try_push('a').is_ok()];
721        assert_eq![3, s.len()];
722    }
723
724    #[test]
725    #[cfg(feature = "_str_u8")]
726    fn pop() {
727        let mut s = StringU8::<3>::new().unwrap();
728        s.push('ñ');
729        s.push('a');
730        assert_eq![Some('a'), s.pop()];
731        assert_eq![Some('ñ'), s.pop()];
732        assert_eq![None, s.pop()];
733    }
734}