ecow/
string.rs

1//! A clone-on-write, small-string-optimized alternative to [`String`].
2
3use alloc::borrow::Cow;
4use core::borrow::Borrow;
5use core::cmp::Ordering;
6use core::fmt::{self, Debug, Display, Formatter, Write};
7use core::hash::{Hash, Hasher};
8use core::ops::{Add, AddAssign, Deref};
9
10#[cfg(not(feature = "std"))]
11use alloc::string::String;
12
13use crate::dynamic::{DynamicVec, InlineVec};
14
15/// Create a new [`EcoString`] from a format string.
16/// ```
17/// # use ecow::eco_format;
18/// assert_eq!(eco_format!("Hello, {}!", 123), "Hello, 123!");
19/// ```
20#[macro_export]
21macro_rules! eco_format {
22    ($($tts:tt)*) => {{
23        use ::std::fmt::Write;
24        let mut s = $crate::EcoString::new();
25        ::std::write!(s, $($tts)*).unwrap();
26        s
27    }};
28}
29
30/// An economical string with inline storage and clone-on-write semantics.
31///
32/// This type has a size of 16 bytes. It has 15 bytes of inline storage and
33/// starting from 16 bytes it becomes an [`EcoVec<u8>`](super::EcoVec). The
34/// internal reference counter of the heap variant is atomic, making this type
35/// [`Sync`] and [`Send`].
36///
37/// # Example
38/// ```
39/// use ecow::EcoString;
40///
41/// // This is stored inline.
42/// let small = EcoString::from("Welcome");
43///
44/// // This spills to the heap only once: `big` and `third` share the same
45/// // underlying allocation. Just like vectors, heap strings are only really
46/// // cloned upon mutation.
47/// let big = small + " to earth! 🌱";
48/// let mut third = big.clone();
49/// assert_eq!(big, "Welcome to earth! 🌱");
50/// assert_eq!(third, big);
51///
52/// // This allocates again to mutate `third` without affecting `big`.
53/// assert_eq!(third.pop(), Some('🌱'));
54/// assert_eq!(third, "Welcome to earth! ");
55/// assert_eq!(big, "Welcome to earth! 🌱");
56/// ```
57///
58/// # Note
59/// The above holds true for normal 32-bit or 64-bit little endian systems. On
60/// 64-bit big-endian systems, the type's size increases to 24 bytes and the
61/// amount of inline storage to 23 bytes.
62#[derive(Clone)]
63pub struct EcoString(DynamicVec);
64
65impl EcoString {
66    /// Maximum number of bytes for an inline `EcoString` before spilling on
67    /// the heap.
68    ///
69    /// The exact value for this is architecture dependent.
70    ///
71    /// # Note
72    /// This value is semver exempt and can be changed with any update.
73    pub const INLINE_LIMIT: usize = crate::dynamic::LIMIT;
74
75    /// Create a new, empty string.
76    #[inline]
77    pub const fn new() -> Self {
78        Self(DynamicVec::new())
79    }
80
81    /// Create a new, inline string.
82    ///
83    /// Panics if the string's length exceeds the capacity of the inline
84    /// storage.
85    #[inline]
86    pub const fn inline(string: &str) -> Self {
87        let Ok(inline) = InlineVec::from_slice(string.as_bytes()) else {
88            exceeded_inline_capacity();
89        };
90        Self(DynamicVec::from_inline(inline))
91    }
92
93    /// Create a new, empty string with the given `capacity`.
94    #[inline]
95    pub fn with_capacity(capacity: usize) -> Self {
96        Self(DynamicVec::with_capacity(capacity))
97    }
98
99    /// Create an instance from a string slice.
100    #[inline]
101    fn from_str(string: &str) -> Self {
102        Self(DynamicVec::from_slice(string.as_bytes()))
103    }
104
105    /// Whether the string is empty.
106    #[inline]
107    pub fn is_empty(&self) -> bool {
108        self.len() == 0
109    }
110
111    /// The length of the string in bytes.
112    #[inline]
113    pub fn len(&self) -> usize {
114        self.0.len()
115    }
116
117    /// A string slice containing the entire string.
118    #[inline]
119    pub fn as_str(&self) -> &str {
120        // Safety:
121        // The buffer contents stem from correct UTF-8 sources:
122        // - Valid ASCII characters
123        // - Other string slices
124        // - Chars that were encoded with char::encode_utf8
125        unsafe { core::str::from_utf8_unchecked(self.0.as_slice()) }
126    }
127
128    /// Produce a mutable slice containing the entire string.
129    ///
130    /// Clones the string if its reference count is larger than 1.
131    #[inline]
132    pub fn make_mut(&mut self) -> &mut str {
133        // Safety:
134        // The buffer contents stem from correct UTF-8 sources:
135        // - Valid ASCII characters
136        // - Other string slices
137        // - Chars that were encoded with char::encode_utf8
138        unsafe { core::str::from_utf8_unchecked_mut(self.0.make_mut()) }
139    }
140
141    /// Append the given character at the end.
142    #[inline]
143    pub fn push(&mut self, c: char) {
144        if c.len_utf8() == 1 {
145            self.0.push(c as u8);
146        } else {
147            self.push_str(c.encode_utf8(&mut [0; 4]));
148        }
149    }
150
151    /// Append the given string slice at the end.
152    pub fn push_str(&mut self, string: &str) {
153        self.0.extend_from_slice(string.as_bytes());
154    }
155
156    /// Remove the last character from the string.
157    #[inline]
158    pub fn pop(&mut self) -> Option<char> {
159        let slice = self.as_str();
160        let c = slice.chars().next_back()?;
161        self.0.truncate(slice.len() - c.len_utf8());
162        Some(c)
163    }
164
165    /// Clear the string.
166    #[inline]
167    pub fn clear(&mut self) {
168        self.0.clear();
169    }
170
171    /// Replaces all matches of a string with another string.
172    ///
173    /// This is a bit less general that [`str::replace`] because the `Pattern`
174    /// trait is unstable. In return, it can produce an `EcoString` without
175    /// any intermediate [`String`] allocation.
176    pub fn replace(&self, pat: &str, to: &str) -> Self {
177        self.replacen(pat, to, usize::MAX)
178    }
179
180    /// Replaces the first N matches of a string with another string.
181    ///
182    /// This is a bit less general that [`str::replacen`] because the `Pattern`
183    /// trait is unstable. In return, it can produce an `EcoString` without
184    /// any intermediate [`String`] allocation.
185    pub fn replacen(&self, pat: &str, to: &str, count: usize) -> Self {
186        // Copied from the standard library: https://github.com/rust-lang/rust
187        let mut result = Self::new();
188        let mut last_end = 0;
189        for (start, part) in self.match_indices(pat).take(count) {
190            // Safety: Copied from std.
191            result.push_str(unsafe { self.get_unchecked(last_end..start) });
192            result.push_str(to);
193            last_end = start + part.len();
194        }
195        // Safety: Copied from std.
196        result.push_str(unsafe { self.get_unchecked(last_end..self.len()) });
197        result
198    }
199
200    /// Returns the lowercase equivalent of this string.
201    pub fn to_lowercase(&self) -> Self {
202        let str = self.as_str();
203        let mut lower = Self::with_capacity(str.len());
204        for c in str.chars() {
205            // Let std handle the special case.
206            if c == 'Σ' {
207                return str.to_lowercase().into();
208            }
209            for v in c.to_lowercase() {
210                lower.push(v);
211            }
212        }
213        lower
214    }
215
216    /// Returns the uppercase equivalent of this string.
217    pub fn to_uppercase(&self) -> Self {
218        let str = self.as_str();
219        let mut upper = Self::with_capacity(str.len());
220        for c in str.chars() {
221            for v in c.to_uppercase() {
222                upper.push(v);
223            }
224        }
225        upper
226    }
227
228    /// Returns a copy of this string where each character is mapped to its
229    /// ASCII uppercase equivalent.
230    pub fn to_ascii_lowercase(&self) -> Self {
231        let mut s = self.clone();
232        s.make_mut().make_ascii_lowercase();
233        s
234    }
235
236    /// Returns a copy of this string where each character is mapped to its
237    /// ASCII uppercase equivalent.
238    pub fn to_ascii_uppercase(&self) -> Self {
239        let mut s = self.clone();
240        s.make_mut().make_ascii_uppercase();
241        s
242    }
243
244    /// Repeat this string `n` times.
245    pub fn repeat(&self, n: usize) -> Self {
246        let slice = self.as_bytes();
247        let capacity = slice.len().saturating_mul(n);
248        let mut vec = DynamicVec::with_capacity(capacity);
249        for _ in 0..n {
250            vec.extend_from_slice(slice);
251        }
252        Self(vec)
253    }
254}
255
256impl Deref for EcoString {
257    type Target = str;
258
259    #[inline]
260    fn deref(&self) -> &str {
261        self.as_str()
262    }
263}
264
265impl Default for EcoString {
266    #[inline]
267    fn default() -> Self {
268        Self::new()
269    }
270}
271
272impl Debug for EcoString {
273    #[inline]
274    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
275        Debug::fmt(self.as_str(), f)
276    }
277}
278
279impl Display for EcoString {
280    #[inline]
281    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
282        Display::fmt(self.as_str(), f)
283    }
284}
285
286impl Eq for EcoString {}
287
288impl PartialEq for EcoString {
289    #[inline]
290    fn eq(&self, other: &Self) -> bool {
291        self.as_str().eq(other.as_str())
292    }
293}
294
295impl PartialEq<str> for EcoString {
296    #[inline]
297    fn eq(&self, other: &str) -> bool {
298        self.as_str().eq(other)
299    }
300}
301
302impl PartialEq<&str> for EcoString {
303    #[inline]
304    fn eq(&self, other: &&str) -> bool {
305        self.as_str().eq(*other)
306    }
307}
308
309impl PartialEq<String> for EcoString {
310    #[inline]
311    fn eq(&self, other: &String) -> bool {
312        self.as_str().eq(other)
313    }
314}
315
316impl PartialEq<EcoString> for str {
317    #[inline]
318    fn eq(&self, other: &EcoString) -> bool {
319        self.eq(other.as_str())
320    }
321}
322
323impl PartialEq<EcoString> for &str {
324    #[inline]
325    fn eq(&self, other: &EcoString) -> bool {
326        (*self).eq(other.as_str())
327    }
328}
329
330impl PartialEq<EcoString> for String {
331    #[inline]
332    fn eq(&self, other: &EcoString) -> bool {
333        self.eq(other.as_str())
334    }
335}
336
337impl Ord for EcoString {
338    #[inline]
339    fn cmp(&self, other: &Self) -> Ordering {
340        self.as_str().cmp(other.as_str())
341    }
342}
343
344impl PartialOrd for EcoString {
345    #[inline]
346    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
347        Some(self.cmp(other))
348    }
349}
350
351impl Hash for EcoString {
352    #[inline]
353    fn hash<H: Hasher>(&self, state: &mut H) {
354        self.as_str().hash(state);
355    }
356}
357
358impl Write for EcoString {
359    #[inline]
360    fn write_str(&mut self, s: &str) -> fmt::Result {
361        self.push_str(s);
362        Ok(())
363    }
364
365    #[inline]
366    fn write_char(&mut self, c: char) -> fmt::Result {
367        self.push(c);
368        Ok(())
369    }
370}
371
372impl Add for EcoString {
373    type Output = Self;
374
375    #[inline]
376    fn add(mut self, rhs: Self) -> Self::Output {
377        self += rhs;
378        self
379    }
380}
381
382impl AddAssign for EcoString {
383    #[inline]
384    fn add_assign(&mut self, rhs: Self) {
385        self.push_str(rhs.as_str());
386    }
387}
388
389impl Add<&str> for EcoString {
390    type Output = Self;
391
392    #[inline]
393    fn add(mut self, rhs: &str) -> Self::Output {
394        self += rhs;
395        self
396    }
397}
398
399impl AddAssign<&str> for EcoString {
400    #[inline]
401    fn add_assign(&mut self, rhs: &str) {
402        self.push_str(rhs);
403    }
404}
405
406impl AsRef<str> for EcoString {
407    #[inline]
408    fn as_ref(&self) -> &str {
409        self
410    }
411}
412
413impl Borrow<str> for EcoString {
414    #[inline]
415    fn borrow(&self) -> &str {
416        self
417    }
418}
419
420impl From<char> for EcoString {
421    #[inline]
422    fn from(c: char) -> Self {
423        Self::inline(c.encode_utf8(&mut [0; 4]))
424    }
425}
426
427impl From<&str> for EcoString {
428    #[inline]
429    fn from(s: &str) -> Self {
430        Self::from_str(s)
431    }
432}
433
434impl From<String> for EcoString {
435    /// When the string does not fit inline, this needs to allocate to change
436    /// the layout.
437    #[inline]
438    fn from(s: String) -> Self {
439        Self::from_str(&s)
440    }
441}
442
443impl From<&String> for EcoString {
444    #[inline]
445    fn from(s: &String) -> Self {
446        Self::from_str(s.as_str())
447    }
448}
449
450impl From<&EcoString> for EcoString {
451    #[inline]
452    fn from(s: &EcoString) -> Self {
453        s.clone()
454    }
455}
456
457impl From<Cow<'_, str>> for EcoString {
458    #[inline]
459    fn from(s: Cow<str>) -> Self {
460        Self::from_str(&s)
461    }
462}
463
464impl FromIterator<char> for EcoString {
465    #[inline]
466    fn from_iter<T: IntoIterator<Item = char>>(iter: T) -> Self {
467        let mut s = Self::new();
468        for c in iter {
469            s.push(c);
470        }
471        s
472    }
473}
474
475impl FromIterator<Self> for EcoString {
476    #[inline]
477    fn from_iter<T: IntoIterator<Item = Self>>(iter: T) -> Self {
478        let mut s = Self::new();
479        for piece in iter {
480            s.push_str(&piece);
481        }
482        s
483    }
484}
485
486impl Extend<char> for EcoString {
487    #[inline]
488    fn extend<T: IntoIterator<Item = char>>(&mut self, iter: T) {
489        for c in iter {
490            self.push(c);
491        }
492    }
493}
494
495impl From<EcoString> for String {
496    /// This needs to allocate to change the layout.
497    #[inline]
498    fn from(s: EcoString) -> Self {
499        s.as_str().into()
500    }
501}
502
503impl From<&EcoString> for String {
504    #[inline]
505    fn from(s: &EcoString) -> Self {
506        s.as_str().into()
507    }
508}
509
510#[cold]
511const fn exceeded_inline_capacity() -> ! {
512    panic!("exceeded inline capacity");
513}
514
515#[cfg(feature = "serde")]
516mod serde {
517    use crate::EcoString;
518    use core::fmt;
519    use serde::de::{Deserializer, Error, Unexpected, Visitor};
520
521    impl serde::Serialize for EcoString {
522        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
523        where
524            S: serde::Serializer,
525        {
526            self.as_str().serialize(serializer)
527        }
528    }
529
530    impl<'de> serde::Deserialize<'de> for EcoString {
531        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
532        where
533            D: Deserializer<'de>,
534        {
535            struct EcoStringVisitor;
536
537            impl<'a> Visitor<'a> for EcoStringVisitor {
538                type Value = EcoString;
539
540                fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
541                    formatter.write_str("a string")
542                }
543
544                fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
545                where
546                    E: Error,
547                {
548                    Ok(EcoString::from(v))
549                }
550
551                fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
552                where
553                    E: Error,
554                {
555                    if let Ok(utf8) = core::str::from_utf8(v) {
556                        return Ok(EcoString::from(utf8));
557                    }
558                    Err(Error::invalid_value(Unexpected::Bytes(v), &self))
559                }
560            }
561
562            deserializer.deserialize_str(EcoStringVisitor)
563        }
564    }
565}