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