1#[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: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 = "A UTF-8–encoded string, backed by an array with [`" $t "::MAX`] bytes of capacity."]
49 #[doc = "Internally, the current length is stored as a [`" $t "`]."]
51 #[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 arr: [u8; CAP],
91 len: $t,
92 }
93
94 impl<const CAP: usize> $name<CAP> {
95 #[doc = "Creates a new empty `String" $t:camel "` with a capacity of `CAP` bytes."]
98 #[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 #[must_use] #[rustfmt::skip]
113 pub const fn len(&self) -> usize { self.len as usize }
114
115 #[must_use] #[rustfmt::skip]
117 pub const fn is_empty(&self) -> bool { self.len == 0 }
118
119 #[must_use] #[rustfmt::skip]
121 pub const fn is_full(&self) -> bool { self.len == CAP as $t }
122
123 #[must_use] #[rustfmt::skip]
125 pub const fn capacity() -> usize { CAP }
126
127 #[must_use] #[rustfmt::skip]
129 pub const fn remaining_capacity(&self) -> usize { CAP - self.len as usize }
130
131 #[must_use] #[rustfmt::skip]
137 pub const fn into_array(self) -> [u8; CAP] { self.arr }
138
139 #[must_use] #[rustfmt::skip]
143 pub const fn as_array(&self) -> [u8; CAP] { self.arr }
144
145 #[must_use] #[rustfmt::skip]
148 pub const fn as_bytes(&self) -> &[u8] { self.arr.split_at(self.len as usize).0 }
149
150 #[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 unsafe { self.arr.get_unchecked_mut(0..self.len as usize) }
163 }
164
165 #[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 unsafe { Str::from_utf8_unchecked(self.as_bytes()) }
177 }
178
179 #[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 #[rustfmt::skip]
190 pub fn chars(&self) -> IterChars { self.as_str().chars() }
191
192 #[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 pub fn clear(&mut self) {
202 self.len = 0;
203 }
204
205 pub fn reset(&mut self) {
207 self.arr = [0; CAP];
208 self.len = 0;
209 }
210
211 #[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 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 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 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 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 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 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 #[doc = "Creates a new `String" $t:camel "` from a `char`."]
327 #[doc = "Returns [`MismatchedCapacity`] if `CAP > `[`" $t "::MAX`]."]
330 #[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 #[doc = "Returns [`MismatchedCapacity`] if `CAP > `[`" $t "::MAX`]."]
349 #[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 #[doc = "Returns [`MismatchedCapacity`] if `CAP > `[`" $t "::MAX`]."]
365 #[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 #[doc = "Returns [`MismatchedCapacity`] if `CAP > `[`" $t
384 "::MAX`]` || CAP < c.`[`len_utf8()`][char16#method.len_utf8]."]
385 #[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 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 #[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 $(
435 #[doc = "This method will only be *const* if the `" $cmp "` feature is enabled."]
437 #[cfg(feature = $cmp)]
438 )? 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 $( #[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 $(
470 #[doc = "This method will only be *const* if the `" $cmp "` feature is enabled."]
472 #[cfg(feature = $cmp)]
473 )? #[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 $( #[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 $(
498 #[doc = "This method will only be *const* if the `" $cmp "` feature is enabled."]
500 #[cfg(feature = $cmp)]
501 )? 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 $( #[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 $(
545 #[doc = "This method will only be *const* if the `" $cmp "` feature is enabled."]
547 #[cfg(feature = $cmp)]
548 )? #[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 $( #[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 impl<const CAP: usize> Default for $name<CAP> {
581 #[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 #[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 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 #[doc = "Returns [`MismatchedCapacity`] if `CAP > `[`" $t "::MAX`]"]
641 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 #[doc = "Returns [`InvalidText::Capacity`] if `CAP > `[`" $t "::MAX`], or if "]
661 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}