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    /// Shortens the string to the specified length.
172    ///
173    /// If `new_len` is greater than or equal to the string's current length,
174    /// this has no effect.
175    ///
176    /// Panics if `new_len` does not lie on a [`char`] boundary.
177    #[inline]
178    pub fn truncate(&mut self, new_len: usize) {
179        if new_len <= self.len() {
180            assert!(self.is_char_boundary(new_len));
181            self.0.truncate(new_len);
182        }
183    }
184
185    /// Replaces all matches of a string with another string.
186    ///
187    /// This is a bit less general that [`str::replace`] because the `Pattern`
188    /// trait is unstable. In return, it can produce an `EcoString` without
189    /// any intermediate [`String`] allocation.
190    pub fn replace(&self, pat: &str, to: &str) -> Self {
191        self.replacen(pat, to, usize::MAX)
192    }
193
194    /// Replaces the first N matches of a string with another string.
195    ///
196    /// This is a bit less general that [`str::replacen`] because the `Pattern`
197    /// trait is unstable. In return, it can produce an `EcoString` without
198    /// any intermediate [`String`] allocation.
199    pub fn replacen(&self, pat: &str, to: &str, count: usize) -> Self {
200        // Copied from the standard library: https://github.com/rust-lang/rust
201        let mut result = Self::new();
202        let mut last_end = 0;
203        for (start, part) in self.match_indices(pat).take(count) {
204            // Safety: Copied from std.
205            result.push_str(unsafe { self.get_unchecked(last_end..start) });
206            result.push_str(to);
207            last_end = start + part.len();
208        }
209        // Safety: Copied from std.
210        result.push_str(unsafe { self.get_unchecked(last_end..self.len()) });
211        result
212    }
213
214    /// Returns the lowercase equivalent of this string.
215    pub fn to_lowercase(&self) -> Self {
216        let str = self.as_str();
217        let mut lower = Self::with_capacity(str.len());
218        for c in str.chars() {
219            // Let std handle the special case.
220            if c == 'Σ' {
221                return str.to_lowercase().into();
222            }
223            for v in c.to_lowercase() {
224                lower.push(v);
225            }
226        }
227        lower
228    }
229
230    /// Returns the uppercase equivalent of this string.
231    pub fn to_uppercase(&self) -> Self {
232        let str = self.as_str();
233        let mut upper = Self::with_capacity(str.len());
234        for c in str.chars() {
235            for v in c.to_uppercase() {
236                upper.push(v);
237            }
238        }
239        upper
240    }
241
242    /// Returns a copy of this string where each character is mapped to its
243    /// ASCII uppercase equivalent.
244    pub fn to_ascii_lowercase(&self) -> Self {
245        let mut s = self.clone();
246        s.make_mut().make_ascii_lowercase();
247        s
248    }
249
250    /// Returns a copy of this string where each character is mapped to its
251    /// ASCII uppercase equivalent.
252    pub fn to_ascii_uppercase(&self) -> Self {
253        let mut s = self.clone();
254        s.make_mut().make_ascii_uppercase();
255        s
256    }
257
258    /// Repeat this string `n` times.
259    pub fn repeat(&self, n: usize) -> Self {
260        let slice = self.as_bytes();
261        let capacity = slice.len().saturating_mul(n);
262        let mut vec = DynamicVec::with_capacity(capacity);
263        for _ in 0..n {
264            vec.extend_from_slice(slice);
265        }
266        Self(vec)
267    }
268}
269
270impl Deref for EcoString {
271    type Target = str;
272
273    #[inline]
274    fn deref(&self) -> &str {
275        self.as_str()
276    }
277}
278
279impl Default for EcoString {
280    #[inline]
281    fn default() -> Self {
282        Self::new()
283    }
284}
285
286impl Debug for EcoString {
287    #[inline]
288    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
289        Debug::fmt(self.as_str(), f)
290    }
291}
292
293impl Display for EcoString {
294    #[inline]
295    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
296        Display::fmt(self.as_str(), f)
297    }
298}
299
300impl Eq for EcoString {}
301
302impl PartialEq for EcoString {
303    #[inline]
304    fn eq(&self, other: &Self) -> bool {
305        self.as_str().eq(other.as_str())
306    }
307}
308
309impl PartialEq<str> for EcoString {
310    #[inline]
311    fn eq(&self, other: &str) -> bool {
312        self.as_str().eq(other)
313    }
314}
315
316impl PartialEq<&str> for EcoString {
317    #[inline]
318    fn eq(&self, other: &&str) -> bool {
319        self.as_str().eq(*other)
320    }
321}
322
323impl PartialEq<String> for EcoString {
324    #[inline]
325    fn eq(&self, other: &String) -> bool {
326        self.as_str().eq(other)
327    }
328}
329
330impl PartialEq<EcoString> for str {
331    #[inline]
332    fn eq(&self, other: &EcoString) -> bool {
333        self.eq(other.as_str())
334    }
335}
336
337impl PartialEq<EcoString> for &str {
338    #[inline]
339    fn eq(&self, other: &EcoString) -> bool {
340        (*self).eq(other.as_str())
341    }
342}
343
344impl PartialEq<EcoString> for String {
345    #[inline]
346    fn eq(&self, other: &EcoString) -> bool {
347        self.eq(other.as_str())
348    }
349}
350
351impl Ord for EcoString {
352    #[inline]
353    fn cmp(&self, other: &Self) -> Ordering {
354        self.as_str().cmp(other.as_str())
355    }
356}
357
358impl PartialOrd for EcoString {
359    #[inline]
360    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
361        Some(self.cmp(other))
362    }
363}
364
365impl Hash for EcoString {
366    #[inline]
367    fn hash<H: Hasher>(&self, state: &mut H) {
368        self.as_str().hash(state);
369    }
370}
371
372impl Write for EcoString {
373    #[inline]
374    fn write_str(&mut self, s: &str) -> fmt::Result {
375        self.push_str(s);
376        Ok(())
377    }
378
379    #[inline]
380    fn write_char(&mut self, c: char) -> fmt::Result {
381        self.push(c);
382        Ok(())
383    }
384}
385
386impl Add for EcoString {
387    type Output = Self;
388
389    #[inline]
390    fn add(mut self, rhs: Self) -> Self::Output {
391        self += rhs;
392        self
393    }
394}
395
396impl AddAssign for EcoString {
397    #[inline]
398    fn add_assign(&mut self, rhs: Self) {
399        self.push_str(rhs.as_str());
400    }
401}
402
403impl Add<&str> for EcoString {
404    type Output = Self;
405
406    #[inline]
407    fn add(mut self, rhs: &str) -> Self::Output {
408        self += rhs;
409        self
410    }
411}
412
413impl AddAssign<&str> for EcoString {
414    #[inline]
415    fn add_assign(&mut self, rhs: &str) {
416        self.push_str(rhs);
417    }
418}
419
420impl AsRef<str> for EcoString {
421    #[inline]
422    fn as_ref(&self) -> &str {
423        self
424    }
425}
426
427impl Borrow<str> for EcoString {
428    #[inline]
429    fn borrow(&self) -> &str {
430        self
431    }
432}
433
434impl From<char> for EcoString {
435    #[inline]
436    fn from(c: char) -> Self {
437        Self::inline(c.encode_utf8(&mut [0; 4]))
438    }
439}
440
441impl From<&str> for EcoString {
442    #[inline]
443    fn from(s: &str) -> Self {
444        Self::from_str(s)
445    }
446}
447
448impl From<String> for EcoString {
449    /// When the string does not fit inline, this needs to allocate to change
450    /// the layout.
451    #[inline]
452    fn from(s: String) -> Self {
453        Self::from_str(&s)
454    }
455}
456
457impl From<&String> for EcoString {
458    #[inline]
459    fn from(s: &String) -> Self {
460        Self::from_str(s.as_str())
461    }
462}
463
464impl From<&EcoString> for EcoString {
465    #[inline]
466    fn from(s: &EcoString) -> Self {
467        s.clone()
468    }
469}
470
471impl From<Cow<'_, str>> for EcoString {
472    #[inline]
473    fn from(s: Cow<str>) -> Self {
474        Self::from_str(&s)
475    }
476}
477
478impl FromIterator<char> for EcoString {
479    #[inline]
480    fn from_iter<T: IntoIterator<Item = char>>(iter: T) -> Self {
481        let mut s = Self::new();
482        for c in iter {
483            s.push(c);
484        }
485        s
486    }
487}
488
489impl FromIterator<Self> for EcoString {
490    #[inline]
491    fn from_iter<T: IntoIterator<Item = Self>>(iter: T) -> Self {
492        let mut s = Self::new();
493        for piece in iter {
494            s.push_str(&piece);
495        }
496        s
497    }
498}
499
500impl Extend<char> for EcoString {
501    #[inline]
502    fn extend<T: IntoIterator<Item = char>>(&mut self, iter: T) {
503        for c in iter {
504            self.push(c);
505        }
506    }
507}
508
509impl From<EcoString> for String {
510    /// This needs to allocate to change the layout.
511    #[inline]
512    fn from(s: EcoString) -> Self {
513        s.as_str().into()
514    }
515}
516
517impl From<&EcoString> for String {
518    #[inline]
519    fn from(s: &EcoString) -> Self {
520        s.as_str().into()
521    }
522}
523
524#[cold]
525const fn exceeded_inline_capacity() -> ! {
526    panic!("exceeded inline capacity");
527}
528
529#[cfg(feature = "serde")]
530mod serde {
531    use crate::EcoString;
532    use core::fmt;
533    use serde::de::{Deserializer, Error, Unexpected, Visitor};
534
535    impl serde::Serialize for EcoString {
536        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
537        where
538            S: serde::Serializer,
539        {
540            self.as_str().serialize(serializer)
541        }
542    }
543
544    impl<'de> serde::Deserialize<'de> for EcoString {
545        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
546        where
547            D: Deserializer<'de>,
548        {
549            struct EcoStringVisitor;
550
551            impl Visitor<'_> for EcoStringVisitor {
552                type Value = EcoString;
553
554                fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
555                    formatter.write_str("a string")
556                }
557
558                fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
559                where
560                    E: Error,
561                {
562                    Ok(EcoString::from(v))
563                }
564
565                fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
566                where
567                    E: Error,
568                {
569                    if let Ok(utf8) = core::str::from_utf8(v) {
570                        return Ok(EcoString::from(utf8));
571                    }
572                    Err(Error::invalid_value(Unexpected::Bytes(v), &self))
573                }
574            }
575
576            deserializer.deserialize_str(EcoStringVisitor)
577        }
578    }
579}