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}