1#[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: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 #[doc = crate::TAG_TEXT!()]
49 #[doc = "A UTF-8–encoded string, backed by an array with [`" $t "::MAX`] bytes of capacity."]
50 #[doc = "Internally, the current length is stored as a [`" $t "`]."]
52 #[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 arr: [u8; CAP],
92 len: $t,
93 }
94
95 impl<const CAP: usize> $name<CAP> {
96 #[doc = "Creates a new empty `String" $t:camel "` with a capacity of `CAP` bytes."]
99 #[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 #[must_use] #[rustfmt::skip]
114 pub const fn len(&self) -> usize { self.len as usize }
115
116 #[must_use] #[rustfmt::skip]
118 pub const fn is_empty(&self) -> bool { self.len == 0 }
119
120 #[must_use] #[rustfmt::skip]
122 pub const fn is_full(&self) -> bool { self.len == CAP as $t }
123
124 #[must_use] #[rustfmt::skip]
126 pub const fn capacity() -> usize { CAP }
127
128 #[must_use] #[rustfmt::skip]
130 pub const fn remaining_capacity(&self) -> usize { CAP - self.len as usize }
131
132 #[must_use] #[rustfmt::skip]
138 pub const fn into_array(self) -> [u8; CAP] { self.arr }
139
140 #[must_use] #[rustfmt::skip]
144 pub const fn as_array(&self) -> [u8; CAP] { self.arr }
145
146 #[must_use] #[rustfmt::skip]
149 pub const fn as_bytes(&self) -> &[u8] { self.arr.split_at(self.len as usize).0 }
150
151 #[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 unsafe { self.arr.get_unchecked_mut(0..self.len as usize) }
164 }
165
166 #[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 unsafe { Str::from_utf8_unchecked(self.as_bytes()) }
178 }
179
180 #[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 #[rustfmt::skip]
191 pub fn chars(&self) -> IterChars<'_> { self.as_str().chars() }
192
193 #[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 pub fn clear(&mut self) {
203 self.len = 0;
204 }
205
206 pub fn reset(&mut self) {
208 self.arr = [0; CAP];
209 self.len = 0;
210 }
211
212 #[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 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 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 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 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 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 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 #[doc = "Creates a new `String" $t:camel "` from a `char`."]
328 #[doc = "Returns [`MismatchedCapacity`] if `CAP > `[`" $t "::MAX`]."]
331 #[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 #[doc = "Returns [`MismatchedCapacity`] if `CAP > `[`" $t "::MAX`]."]
350 #[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 #[doc = "Returns [`MismatchedCapacity`] if `CAP > `[`" $t "::MAX`]."]
366 #[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 #[doc = "Returns [`MismatchedCapacity`] if `CAP > `[`" $t
385 "::MAX`]` || CAP < c.`[`len_utf8()`][char16#method.len_utf8]."]
386 #[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 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 #[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 $(
436 #[doc = "This method will only be *const* if the `" $cmp "` feature is enabled."]
438 #[cfg(feature = $cmp)]
439 )? 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 $( #[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 $(
471 #[doc = "This method will only be *const* if the `" $cmp "` feature is enabled."]
473 #[cfg(feature = $cmp)]
474 )? #[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 $( #[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 $(
499 #[doc = "This method will only be *const* if the `" $cmp "` feature is enabled."]
501 #[cfg(feature = $cmp)]
502 )? 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 $( #[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 $(
546 #[doc = "This method will only be *const* if the `" $cmp "` feature is enabled."]
548 #[cfg(feature = $cmp)]
549 )? #[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 $( #[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 impl<const CAP: usize> Default for $name<CAP> {
582 #[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 #[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 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 #[doc = "Returns [`MismatchedCapacity`] if `CAP > `[`" $t "::MAX`]"]
642 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 #[doc = "Returns [`InvalidText::Capacity`] if `CAP > `[`" $t "::MAX`], or if "]
662 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}