devela/text/str/ext_str.rs
1// devela::text::ext::slice
2//
3//! Defines the [`ExtStr`] trait.
4//
5// WAIT: [str_as_str](https://github.com/rust-lang/rust/issues/130366)
6// WAIT: [substr_range](https://github.com/rust-lang/rust/issues/126769)
7// IMPROVE: use `NumToStr`
8
9use crate::{iif, Ascii, Slice, Str};
10#[cfg(feature = "alloc")]
11use crate::{Arc, Box, Rc};
12crate::_use! {basic::from_utf8}
13
14/// Marker trait to prevent downstream implementations of the [`ExtStr`] trait.
15trait Sealed {}
16impl Sealed for str {}
17
18/// Extension trait providing additional methods for [`&str`].
19#[cfg_attr(feature = "nightly_doc", doc(notable_trait))]
20#[expect(private_bounds, reason = "Sealed")]
21pub trait ExtStr: Sealed {
22 /// Converts the string slice into a `Box<str>`.
23 ///
24 /// Allows single ownership with exact allocation,
25 /// for when you don't need to clone or share.
26 #[cfg(feature = "alloc")]
27 fn to_box(&self) -> Box<str>;
28
29 /// Converts the string slice into an `Rc<str>`.
30 ///
31 /// Allows shared ownership with reference counting,
32 /// reducing memory duplication in single-threaded scenarios.
33 #[cfg(feature = "alloc")]
34 fn to_rc(&self) -> Rc<str>;
35
36 /// Converts the string slice into an `Arc<str>`.
37 ///
38 /// When you need shared ownership of a string slice across multiple threads.
39 #[cfg(feature = "alloc")]
40 fn to_arc(&self) -> Arc<str>;
41
42 /// Repeats a string a given number of times into the provided `buffer`.
43 /// and returns a reference to the new `&str`.
44 /// # Examples
45 /// ```
46 /// use devela::ExtStr;
47 ///
48 /// let mut buf = [0_u8; 12];
49 /// let repeated = "ay".repeat_into(3, &mut buf);
50 /// assert_eq![repeated, "ayayay"];
51 /// ```
52 /// # Features
53 /// Makes use of the `unsafe_str` feature if enabled.
54 ///
55 /// For the *const* version see [`Str::repeat_into`].
56 #[must_use]
57 fn repeat_into<'input, const CAP: usize>(
58 &self,
59 n: usize,
60 buffer: &'input mut [u8; CAP],
61 ) -> &'input str;
62
63 /// Returns a [`&str`] backed by a `buffer`, where you always know each
64 /// character's position.
65 ///
66 /// A [*counter string*][0] is a graduated string of arbitrary `length`,
67 /// with a `separator` positioned after the immediately preceding number.
68 /// # Examples
69 /// ```
70 /// use devela::ExtStr;
71 ///
72 /// let mut buf = [0; 15];
73 /// assert_eq!("2*4*6*8*11*14*", str::new_counter(&mut buf, 14, '*'));
74 /// assert_eq!("_3_5_7_9_12_15_", str::new_counter(&mut buf, 15, '_'));
75 /// ```
76 /// # Panics
77 /// Panics if `buffer.len() < length`, or if `!char.is_ascii()`.
78 ///
79 /// # Features
80 /// Makes use of the `unsafe_str` feature if enabled.
81 ///
82 /// For the *const* version see [`Str::new_counter`].
83 ///
84 /// [0]: https://www.satisfice.com/blog/archives/22
85 #[must_use]
86 fn new_counter(buffer: &mut [u8], length: usize, separator: char) -> &str;
87}
88
89impl ExtStr for str {
90 #[cfg(feature = "alloc")]
91 fn to_box(&self) -> Box<str> {
92 Box::from(self)
93 }
94 #[cfg(feature = "alloc")]
95 fn to_rc(&self) -> Rc<str> {
96 Rc::from(self)
97 }
98 #[cfg(feature = "alloc")]
99 fn to_arc(&self) -> Arc<str> {
100 Arc::from(self)
101 }
102
103 fn repeat_into<'input, const CAP: usize>(
104 &self,
105 n: usize,
106 buffer: &'input mut [u8; CAP],
107 ) -> &'input str {
108 // Str::repeat_into(self, n, buffer) // BENCH
109
110 let s_bytes = self.as_bytes();
111 let mut index = 0;
112 for _ in 0..n {
113 for &b in s_bytes {
114 iif![index == CAP; break];
115 buffer[index] = b;
116 index += 1;
117 }
118 }
119 #[cfg(any(feature = "safe_text", not(feature = "unsafe_str")))]
120 return from_utf8(&buffer[..index]).unwrap();
121 #[cfg(all(not(feature = "safe_text"), feature = "unsafe_str"))]
122 // SAFETY: since self is a valid &str, checks are unneeded.
123 unsafe {
124 Str::from_utf8_unchecked(&buffer[..index])
125 }
126 }
127
128 fn new_counter(buffer: &mut [u8], length: usize, separator: char) -> &str {
129 assert![buffer.len() >= length];
130 if length == 0 {
131 Str::new_cold_empty()
132 } else {
133 let separator = separator as u8;
134 let mut index = length - 1; // start writing from the end
135 let mut num = length; // the first number to write is the length
136 let mut separator_turn = true; // start writing the separator
137
138 let mut num_buf = Ascii(num).digits();
139 let mut num_bytes = Slice::trim_leading_bytes(&num_buf, b'0');
140 // IMPROVE:BENCH use NumToStr
141 // let mut num_buf = [0u8; 22];
142 // let mut num_bytes = num.to_bytes_base(10, &mut num_buf);
143
144 let mut num_len = num_bytes.len();
145
146 loop {
147 if separator_turn {
148 buffer[index] = separator;
149 } else {
150 iif![index > 0; index -= num_len - 1];
151 buffer[index..(num_len + index)].copy_from_slice(&num_bytes[..num_len]);
152
153 num = index;
154
155 num_buf = Ascii(num).digits();
156 num_bytes = Slice::trim_leading_bytes(&num_buf, b'0');
157 // IMPROVE: use NumToStr
158 // num_bytes = num.to_bytes_base(10, &mut num_buf);
159
160 num_len = num_bytes.len();
161 }
162 iif![index == 0; break; index -= 1];
163 separator_turn = !separator_turn;
164 }
165
166 #[cfg(any(feature = "safe_text", not(feature = "unsafe_str")))]
167 return from_utf8(&buffer[..length]).unwrap();
168 #[cfg(all(not(feature = "safe_text"), feature = "unsafe_str"))]
169 // SAFETY: We are only using with Ascii characters
170 return unsafe { Str::from_utf8_unchecked(&buffer[..length]) };
171 }
172 }
173}