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