Skip to main content

devela/text/str/ext/
alloc.rs

1// devela/src/text/str/ext/alloc.rs
2//
3//! Defines the [`StringExt`] trait.
4//
5
6#[cfg(feature = "alloc")]
7use crate::{Arc, Box, Rc, Str, String};
8
9/// Marker trait to prevent downstream implementations of the [`StringExt`] trait.
10#[cfg(feature = "alloc")]
11trait Sealed {}
12#[cfg(feature = "alloc")]
13impl Sealed for String {}
14
15#[doc = crate::_tags!(string)]
16/// Extension trait providing additional methods for [`String`].
17#[expect(private_bounds, reason = "Sealed")]
18#[cfg(feature = "alloc")]
19#[cfg_attr(nightly_doc, doc(notable_trait, cfg(feature = "alloc")))]
20pub trait StringExt: Sealed {
21    /// Converts the string into a `Box<str>`.
22    ///
23    /// Allows single ownership with exact allocation,
24    /// for when you don't need to clone or share.
25    fn to_box(self) -> Box<str>;
26
27    /// Converts the string into an `Rc<str>`.
28    ///
29    /// Allows shared ownership with reference counting,
30    /// reducing memory duplication in single-threaded scenarios.
31    fn to_rc(self) -> Rc<str>;
32
33    /// Converts the string into an `Arc<str>`.
34    ///
35    /// When you need shared ownership of a string slice across multiple threads.
36    fn to_arc(self) -> Arc<str>;
37
38    /// Returns a [`String`] where you always know each character's position.
39    ///
40    /// A [*counter string*][0] is a graduated string of arbitrary `length`,
41    /// with a `separator` positioned after the immediately preceding number.
42    ///
43    /// # Examples
44    /// ```
45    /// use devela::StringExt;
46    ///
47    /// assert_eq!("2*4*6*8*11*14*", String::new_counter(14, '*'));
48    /// assert_eq!("_3_5_7_9_12_15_", String::new_counter(15, '_'));
49    /// ```
50    /// # Panics
51    /// Panics if `!separator.is_ascii()`.
52    ///
53    /// # Features
54    /// `unsafe_str` enables unchecked UTF-8 conversion.
55    ///
56    /// [0]: https://www.satisfice.com/blog/archives/22
57    #[must_use]
58    fn new_counter(length: usize, separator: char) -> String;
59}
60
61#[cfg(feature = "alloc")]
62impl StringExt for String {
63    /// It just calls the method [`String::into_boxed_str`].
64    fn to_box(self) -> Box<str> {
65        self.into_boxed_str()
66    }
67    fn to_rc(self) -> Rc<str> {
68        Rc::from(self)
69    }
70    fn to_arc(self) -> Arc<str> {
71        Arc::from(self)
72    }
73
74    fn new_counter(length: usize, separator: char) -> String {
75        cfg_select! { all(feature = "unsafe_str", not(feature = "safe_text")) => {
76            let mut s = String::with_capacity(length);
77            // SAFETY: only ASCII bytes are written and the str.len() is set to the written prefix
78            unsafe {
79                let buf = s.as_mut_vec();
80                buf.resize(length, 0);
81                let out = Str::new_counter(buf, length, separator);
82                let len = out.len();
83                buf.truncate(len);
84            }
85            s
86        } _ => {
87            let mut buf = crate::vec_![0u8; length];
88            let s = Str::new_counter(&mut buf, length, separator);
89            let len = s.len();
90            buf.truncate(len);
91            String::from_utf8(buf).expect("counter string is guaranteed ASCII")
92        }}
93    }
94}