Skip to main content

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