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}