smart_string/smart_string/
mod.rs

1use std::borrow::Borrow;
2use std::borrow::Cow;
3use std::cmp;
4use std::fmt;
5use std::hash::Hash;
6use std::hash::Hasher;
7use std::ops;
8use std::rc::Rc;
9use std::string::FromUtf16Error;
10use std::string::FromUtf8Error;
11use std::sync::Arc;
12
13use crate::pascal_string;
14use crate::DisplayExt;
15use crate::PascalString;
16
17#[cfg(feature = "serde")]
18mod with_serde;
19
20pub const DEFAULT_CAPACITY: usize = 30;
21
22#[derive(Clone)]
23pub enum SmartString<const N: usize = DEFAULT_CAPACITY> {
24    Heap(String),
25    Stack(PascalString<N>),
26}
27
28impl<const N: usize> SmartString<N> {
29    #[inline]
30    #[must_use]
31    pub const fn new() -> Self {
32        Self::Stack(PascalString::new())
33    }
34
35    #[cfg(not(no_global_oom_handling))]
36    #[inline]
37    #[must_use]
38    pub fn with_capacity(capacity: usize) -> Self {
39        if capacity <= N {
40            Self::new()
41        } else {
42            Self::Heap(String::with_capacity(capacity))
43        }
44    }
45
46    #[inline]
47    pub fn from_utf8(vec: Vec<u8>) -> Result<Self, FromUtf8Error> {
48        String::from_utf8(vec).map(Self::Heap)
49    }
50
51    // TBD What to do with this?
52    // #[cfg(not(no_global_oom_handling))]
53    // #[inline]
54    // #[must_use]
55    // pub fn from_utf8_lossy(v: &[u8]) -> Cow<'_, str> {
56    //     match String::from_utf8_lossy(v) {
57    //         Cow::Borrowed(s) => Cow::Borrowed(s),
58    //         Cow::Owned(s) => Cow::Owned(Self::Heap(s)),
59    //     }
60    // }
61
62    #[cfg(not(no_global_oom_handling))]
63    pub fn from_utf16(v: &[u16]) -> Result<Self, FromUtf16Error> {
64        String::from_utf16(v).map(Self::Heap)
65    }
66
67    #[cfg(not(no_global_oom_handling))]
68    #[must_use]
69    #[inline]
70    pub fn from_utf16_lossy(v: &[u16]) -> Self {
71        Self::Heap(String::from_utf16_lossy(v))
72    }
73
74    #[inline]
75    #[must_use]
76    pub fn as_str(&self) -> &str {
77        self
78    }
79
80    #[inline]
81    #[must_use]
82    pub fn as_mut_str(&mut self) -> &mut str {
83        self
84    }
85
86    #[inline]
87    pub fn is_heap(&self) -> bool {
88        matches!(self, Self::Heap(_))
89    }
90
91    #[inline]
92    pub fn is_stack(&self) -> bool {
93        matches!(self, Self::Stack(_))
94    }
95
96    #[inline]
97    #[must_use]
98    pub fn into_heap(self) -> Self {
99        Self::Heap(match self {
100            Self::Stack(s) => s.to_string(),
101            Self::Heap(s) => s,
102        })
103    }
104
105    #[inline]
106    #[must_use]
107    pub fn try_into_stack(self) -> Self {
108        match self {
109            Self::Stack(s) => Self::Stack(s),
110            Self::Heap(s) => match PascalString::try_from(s.as_str()) {
111                Ok(s) => Self::Stack(s),
112                Err(pascal_string::TryFromStrError::TooLong) => Self::Heap(s),
113            },
114        }
115    }
116
117    #[inline]
118    pub fn push_str(&mut self, string: &str) {
119        match self {
120            Self::Heap(s) => s.push_str(string),
121            Self::Stack(s) => match s.try_push_str(string) {
122                Ok(()) => (),
123                Err(pascal_string::TryFromStrError::TooLong) => {
124                    let mut new = String::with_capacity(s.len() + string.len());
125                    new.push_str(s.as_str());
126                    new.push_str(string);
127                    *self = Self::Heap(new);
128                }
129            },
130        }
131    }
132
133    #[inline]
134    pub fn capacity(&self) -> usize {
135        match self {
136            Self::Heap(s) => s.capacity(),
137            Self::Stack(s) => s.capacity(),
138        }
139    }
140
141    #[cfg(not(no_global_oom_handling))]
142    #[inline]
143    pub fn reserve(&mut self, additional: usize) {
144        match self {
145            Self::Heap(s) => s.reserve(additional),
146            Self::Stack(s) => {
147                if s.capacity() - s.len() < additional {
148                    let mut new = String::with_capacity(s.len() + additional);
149                    new.push_str(s.as_str());
150                    *self = Self::Heap(new);
151                }
152            }
153        }
154    }
155
156    #[cfg(not(no_global_oom_handling))]
157    pub fn reserve_exact(&mut self, additional: usize) {
158        match self {
159            Self::Heap(s) => s.reserve_exact(additional),
160            Self::Stack(s) => {
161                if s.capacity() - s.len() < additional {
162                    let mut new = String::new();
163                    new.reserve_exact(s.len() + additional);
164                    new.push_str(s.as_str());
165                    *self = Self::Heap(new);
166                }
167            }
168        }
169    }
170
171    #[rustversion::since(1.57)]
172    pub fn try_reserve(
173        &mut self,
174        additional: usize,
175    ) -> Result<(), std::collections::TryReserveError> {
176        match self {
177            Self::Heap(s) => s.try_reserve(additional),
178            Self::Stack(s) => {
179                if s.capacity() - s.len() < additional {
180                    let mut new = String::new();
181                    new.try_reserve(s.len() + additional)?;
182                    new.push_str(s.as_str());
183                    *self = Self::Heap(new);
184                }
185                Ok(())
186            }
187        }
188    }
189
190    #[rustversion::since(1.57)]
191    pub fn try_reserve_exact(
192        &mut self,
193        additional: usize,
194    ) -> Result<(), std::collections::TryReserveError> {
195        match self {
196            Self::Heap(s) => s.try_reserve_exact(additional),
197            Self::Stack(s) => {
198                if s.capacity() - s.len() < additional {
199                    let mut new = String::new();
200                    new.try_reserve_exact(s.len() + additional)?;
201                    new.push_str(s.as_str());
202                    *self = Self::Heap(new);
203                }
204                Ok(())
205            }
206        }
207    }
208
209    #[cfg(not(no_global_oom_handling))]
210    #[inline]
211    pub fn shrink_to_fit(&mut self) {
212        match self {
213            Self::Heap(s) => s.shrink_to_fit(),
214            Self::Stack(_) => (),
215        }
216    }
217
218    #[cfg(not(no_global_oom_handling))]
219    #[inline]
220    pub fn shrink_to(&mut self, min_capacity: usize) {
221        match self {
222            Self::Heap(s) => s.shrink_to(min_capacity),
223            Self::Stack(_) => (),
224        }
225    }
226
227    #[cfg(not(no_global_oom_handling))]
228    pub fn push(&mut self, ch: char) {
229        match self {
230            Self::Heap(s) => s.push(ch),
231            Self::Stack(s) => match s.try_push(ch) {
232                Ok(()) => (),
233                Err(pascal_string::TryFromStrError::TooLong) => {
234                    let mut new = String::with_capacity(s.len() + ch.len_utf8());
235                    new.push_str(s.as_str());
236                    new.push(ch);
237                    *self = Self::Heap(new);
238                }
239            },
240        }
241    }
242
243    #[inline]
244    pub fn truncate(&mut self, new_len: usize) {
245        match self {
246            Self::Heap(s) => s.truncate(new_len),
247            Self::Stack(s) => s.truncate(new_len),
248        }
249    }
250
251    #[inline]
252    pub fn pop(&mut self) -> Option<char> {
253        match self {
254            Self::Heap(s) => s.pop(),
255            Self::Stack(s) => s.pop(),
256        }
257    }
258
259    #[inline]
260    pub fn clear(&mut self) {
261        match self {
262            Self::Heap(s) => s.clear(),
263            Self::Stack(s) => s.clear(),
264        }
265    }
266}
267
268// -- Common traits --------------------------------------------------------------------------------
269
270impl<const N: usize> Default for SmartString<N> {
271    #[inline]
272    fn default() -> Self {
273        Self::new()
274    }
275}
276
277impl<T: ops::Deref<Target = str> + ?Sized, const CAPACITY: usize> PartialEq<T>
278    for SmartString<CAPACITY>
279{
280    #[inline(always)]
281    fn eq(&self, other: &T) -> bool {
282        self.as_str().eq(other.deref())
283    }
284}
285
286macro_rules! impl_reverse_eq_for_str_types {
287    ($($t:ty),*) => {
288        $(
289            impl<const N: usize> PartialEq<SmartString<N>> for $t {
290                #[inline(always)]
291                fn eq(&self, other: &SmartString<N>) -> bool {
292                    let a: &str = self.as_ref();
293                    let b = other.as_str();
294                    a.eq(b)
295                }
296            }
297
298            impl<const N: usize> PartialEq<SmartString<N>> for &$t {
299                #[inline(always)]
300                fn eq(&self, other: &SmartString<N>) -> bool {
301                    let a: &str = self.as_ref();
302                    let b = other.as_str();
303                    a.eq(b)
304                }
305            }
306
307            impl<const N: usize> PartialEq<SmartString<N>> for &mut $t {
308                #[inline(always)]
309                fn eq(&self, other: &SmartString<N>) -> bool {
310                    let a: &str = self.as_ref();
311                    let b = other.as_str();
312                    a.eq(b)
313                }
314            }
315        )*
316    };
317}
318
319impl_reverse_eq_for_str_types!(String, str, Cow<'_, str>, Box<str>, Rc<str>, Arc<str>);
320
321impl<const M: usize, const N: usize> PartialEq<SmartString<N>> for &PascalString<M> {
322    #[inline(always)]
323    fn eq(&self, other: &SmartString<N>) -> bool {
324        let a: &str = self.as_ref();
325        let b = other.as_str();
326        a.eq(b)
327    }
328}
329
330impl<const M: usize, const N: usize> PartialEq<SmartString<N>> for &mut PascalString<M> {
331    #[inline(always)]
332    fn eq(&self, other: &SmartString<N>) -> bool {
333        let a: &str = self.as_ref();
334        let b = other.as_str();
335        a.eq(b)
336    }
337}
338
339impl<const N: usize> Eq for SmartString<N> {}
340
341impl<T: ops::Deref<Target = str>, const N: usize> PartialOrd<T> for SmartString<N> {
342    #[inline(always)]
343    fn partial_cmp(&self, other: &T) -> Option<cmp::Ordering> {
344        self.as_str().partial_cmp(other.deref())
345    }
346}
347
348impl<const N: usize> Ord for SmartString<N> {
349    #[inline(always)]
350    fn cmp(&self, other: &Self) -> cmp::Ordering {
351        self.as_str().cmp(other.as_str())
352    }
353}
354
355impl<const N: usize> Hash for SmartString<N> {
356    #[inline(always)]
357    fn hash<H: Hasher>(&self, state: &mut H) {
358        self.as_str().hash(state)
359    }
360}
361
362// -- Formatting -----------------------------------------------------------------------------------
363
364impl<const N: usize> fmt::Debug for SmartString<N> {
365    #[inline(always)]
366    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
367        let name: PascalString<39> = format_args!("SmartString<{N}>")
368            .try_to_fmt()
369            .unwrap_or_else(|_| "SmartString<?>".to_fmt());
370        f.debug_tuple(&name).field(&self.as_str()).finish()
371    }
372}
373
374impl<const N: usize> fmt::Display for SmartString<N> {
375    #[inline]
376    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
377        match self {
378            Self::Heap(s) => s.fmt(f),
379            Self::Stack(s) => s.fmt(f),
380        }
381    }
382}
383
384// -- Reference ------------------------------------------------------------------------------------
385
386impl<const N: usize> ops::Deref for SmartString<N> {
387    type Target = str;
388
389    #[inline]
390    fn deref(&self) -> &Self::Target {
391        match self {
392            Self::Heap(s) => s.deref(),
393            Self::Stack(s) => s.deref(),
394        }
395    }
396}
397
398impl<const N: usize> ops::DerefMut for SmartString<N> {
399    #[inline]
400    fn deref_mut(&mut self) -> &mut Self::Target {
401        match self {
402            Self::Heap(s) => s.deref_mut(),
403            Self::Stack(s) => s.deref_mut(),
404        }
405    }
406}
407
408impl<const N: usize> Borrow<str> for SmartString<N> {
409    #[inline(always)]
410    fn borrow(&self) -> &str {
411        self
412    }
413}
414
415impl<const N: usize> AsRef<str> for SmartString<N> {
416    #[inline(always)]
417    fn as_ref(&self) -> &str {
418        self
419    }
420}
421
422impl<const N: usize> AsRef<[u8]> for SmartString<N> {
423    #[inline(always)]
424    fn as_ref(&self) -> &[u8] {
425        self.as_bytes()
426    }
427}
428
429// -- Conversion -----------------------------------------------------------------------------------
430
431impl<const N: usize> From<String> for SmartString<N> {
432    #[inline]
433    fn from(s: String) -> Self {
434        Self::Heap(s)
435    }
436}
437
438impl<const M: usize, const N: usize> From<PascalString<M>> for SmartString<N> {
439    #[inline]
440    fn from(s: PascalString<M>) -> Self {
441        PascalString::try_from(s.as_str())
442            .map(Self::Stack)
443            .unwrap_or_else(|pascal_string::TryFromStrError::TooLong| Self::Heap(s.to_string()))
444    }
445}
446
447impl<const N: usize> From<&str> for SmartString<N> {
448    #[inline]
449    fn from(s: &str) -> Self {
450        PascalString::try_from(s)
451            .map(Self::Stack)
452            .unwrap_or_else(|pascal_string::TryFromStrError::TooLong| Self::Heap(String::from(s)))
453    }
454}
455
456impl<const N: usize> From<&mut str> for SmartString<N> {
457    #[inline]
458    fn from(s: &mut str) -> Self {
459        Self::from(&*s)
460    }
461}
462
463impl<const N: usize> From<Cow<'_, str>> for SmartString<N> {
464    #[inline]
465    fn from(s: Cow<'_, str>) -> Self {
466        match s {
467            Cow::Borrowed(s) => Self::from(s),
468            Cow::Owned(s) => Self::Heap(s),
469        }
470    }
471}
472
473impl<const N: usize> FromIterator<char> for SmartString<N> {
474    fn from_iter<T: IntoIterator<Item = char>>(iter: T) -> Self {
475        let mut s = Self::new();
476        // TODO s.extend(iter);
477        for ch in iter {
478            s.push(ch);
479        }
480        s
481    }
482}
483
484impl<'a, const N: usize> FromIterator<&'a str> for SmartString<N> {
485    fn from_iter<T: IntoIterator<Item = &'a str>>(iter: T) -> Self {
486        let mut s = Self::new();
487        // TODO s.extend(iter);
488        for string in iter {
489            s.push_str(string);
490        }
491        s
492    }
493}
494
495// -- IO -------------------------------------------------------------------------------------------
496
497impl<const N: usize> fmt::Write for SmartString<N> {
498    #[inline]
499    fn write_str(&mut self, s: &str) -> fmt::Result {
500        Ok(self.push_str(s))
501    }
502}
503
504// -- ops ------------------------------------------------------------------------------------------
505
506impl<const N: usize, T: ops::Deref<Target = str>> ops::Add<T> for SmartString<N> {
507    type Output = Self;
508
509    #[inline]
510    fn add(mut self, rhs: T) -> Self::Output {
511        self.push_str(&*rhs);
512        self
513    }
514}
515
516// -- Tests ----------------------------------------------------------------------------------------
517
518#[cfg(test)]
519mod tests {
520    use std::mem;
521
522    use super::*;
523
524    #[test]
525    fn test_size() {
526        // Default stack capacity is 30 bytes, corresponding to 32 bytes of the enum.
527        assert_eq!(mem::size_of::<SmartString>(), 32);
528
529        // The minimal size of the enum is 24 bytes, even if the stack variant capacity
530        // is less than 15 bytes.
531        assert_eq!(mem::size_of::<SmartString<0>>(), 24);
532        assert_eq!(mem::size_of::<SmartString<1>>(), 24);
533        assert_eq!(mem::size_of::<SmartString<15>>(), 24);
534
535        // It is unclear why the size of the enum grows to 32 bytes
536        // starting from 17 bytes of size for the stack variant.
537        assert_eq!(mem::size_of::<SmartString<16>>(), 32);
538        assert_eq!(mem::size_of::<SmartString<22>>(), 32);
539
540        // The size of the enum is expected to be 32 bytes for the following capacities.
541        assert_eq!(mem::size_of::<SmartString<23>>(), 32);
542        assert_eq!(mem::size_of::<SmartString<30>>(), 32);
543
544        // Additional bytes of capacity increases the size of the enum by size of a pointer
545        // (8 bytes on 64-bit platforms) by steps of size of a pointer.
546        assert_eq!(mem::size_of::<SmartString<31>>(), 40);
547        assert_eq!(mem::size_of::<SmartString<38>>(), 40);
548
549        assert_eq!(mem::size_of::<SmartString<39>>(), 48);
550        assert_eq!(mem::size_of::<SmartString<46>>(), 48);
551    }
552}