tiny_str/
lib.rs

1/*  Copyright (C) 2025 Saúl Valdelvira
2 *
3 *  This program is free software: you can redistribute it and/or modify
4 *  it under the terms of the GNU General Public License as published by
5 *  the Free Software Foundation, version 3.
6 *
7 *  This program is distributed in the hope that it will be useful,
8 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
9 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10 *  GNU General Public License for more details.
11 *
12 *  You should have received a copy of the GNU General Public License
13 *  along with this program.  If not, see <https://www.gnu.org/licenses/>. */
14
15//! Tiny string
16//!
17//! A string that can store a small amount of bytes on the stack.
18//!
19//! This struct provides a string-like API, but performs SSO (Small String Optimization)
20//! This means that a `TinyString<N>` stores up to N bytes on the stack.
21//! If the string grows bigger than that, it moves the contents to the heap.
22//!
23//! # Example
24//! ```
25//! use tiny_str::TinyString;
26//!
27//! let mut s = TinyString::<10>::new();
28//!
29//! for (i, c) in (b'0'..=b'9').enumerate() {
30//!     s.push(c as char);
31//!     assert_eq!(s.len(), i + 1);
32//! }
33//!
34//! // Up to this point, no heap allocations are needed.
35//! // The string is stored on the stack.
36//!
37//! s.push_str("abc"); // This moves the string to the heap
38//!
39//! assert_eq!(&s[..], "0123456789abc")
40//! ```
41//!
42//! # Memory layout
43//! TinyString is based on [TinyVec], just like [std::string::String] if based
44//! on [std::vec::Vec].
45//!
46//! You can read the [tiny_vec] crate documentation to learn about the internal
47//! representation of the data.
48
49use core::ops::{Deref, DerefMut};
50use core::str::Utf8Error;
51
52use tiny_vec::TinyVec;
53
54const MAX_N_STACK_ELEMENTS: usize = tiny_vec::n_elements_for_stack::<u8>();
55
56/// A string that can store a small amount of bytes on the stack.
57pub struct TinyString<const N: usize = MAX_N_STACK_ELEMENTS>(TinyVec<u8, N>);
58
59impl<const N: usize> TinyString<N> {
60
61    /// Creates a new [TinyString]
62    #[inline]
63    pub const fn new() -> Self {
64        Self(TinyVec::new())
65    }
66
67    /// Creates a new [TinyString] with the given capacity
68    pub fn with_capacity(cap: usize) -> Self {
69        Self(TinyVec::with_capacity(cap))
70    }
71
72    /// Creates a new [TinyString] from the given utf8 buffer.
73    ///
74    /// # Errors
75    /// If the byte buffer contains invalid uft8
76    pub fn from_utf8(utf8: Vec<u8>) -> Result<Self,Utf8Error> {
77        str::from_utf8(&utf8)?;
78        Ok(Self(utf8.into()))
79    }
80
81    /// Creates a new [TinyString] from the given utf8 buffer.
82    ///
83    /// # Safety
84    /// The caller must ensure that the given contains valid utf8
85    #[inline(always)]
86    pub const unsafe fn from_utf8_unchecked(utf8: TinyVec<u8, N>) -> Self {
87        Self(utf8)
88    }
89
90    /// Returns the number of elements inside this string
91    #[inline]
92    pub const fn len(&self) -> usize { self.0.len() }
93
94    /// Returns true if the string is empty
95    #[inline]
96    pub const fn is_empty(&self) -> bool { self.0.is_empty() }
97
98    /// Returns the allocated capacity for this string
99    #[inline]
100    pub const fn capacity(&self) -> usize { self.0.capacity() }
101
102    /// Pushes a character into the string
103    pub fn push(&mut self, c: char) {
104        let len = c.len_utf8();
105        if len == 1 {
106            self.0.push(c as u8);
107        } else {
108            let mut buf = [0_u8; 4];
109            c.encode_utf8(&mut buf);
110            self.0.push_slice(&buf[..len]);
111        }
112    }
113
114    /// Pushes a str slice into this string
115    #[inline]
116    pub fn push_str(&mut self, s: &str) {
117        self.0.push_slice(s.as_bytes());
118    }
119
120    /// Shrinks the capacity of this string to fit exactly it's length
121    #[inline]
122    pub fn shrink_to_fit(&mut self) {
123        self.0.shrink_to_fit();
124    }
125}
126
127impl<const N: usize> Default for TinyString<N> {
128    fn default() -> Self {
129        Self::new()
130    }
131}
132
133impl<const N: usize> Deref for TinyString<N> {
134    type Target = str;
135
136    fn deref(&self) -> &Self::Target {
137        unsafe { str::from_utf8_unchecked(&self.0) }
138    }
139}
140
141impl<const N: usize> DerefMut for TinyString<N> {
142    fn deref_mut(&mut self) -> &mut Self::Target {
143        unsafe { str::from_utf8_unchecked_mut(&mut self.0) }
144    }
145}
146
147impl<S, const N: usize> From<S> for TinyString<N>
148where
149    S: AsRef<str>,
150{
151    fn from(value: S) -> Self {
152        let value = value.as_ref();
153        let mut s = Self::with_capacity(value.len());
154        s.push_str(value);
155        s
156    }
157}
158
159impl<const N: usize> FromIterator<char> for TinyString<N> {
160    fn from_iter<T: IntoIterator<Item = char>>(iter: T) -> Self {
161        let iter = iter.into_iter();
162        let cap = match iter.size_hint() {
163            (_, Some(n)) => n,
164            (n, _) => n,
165        };
166        let mut s = TinyString::with_capacity(cap);
167        for c in iter {
168            s.push(c);
169        }
170        s
171    }
172}
173
174#[cfg(test)]
175mod test;