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}