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