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 [alloc::string::String] if based
44//! on [alloc::vec::Vec].
45//!
46//! You can read the [tiny_vec] crate documentation to learn about the internal
47//! representation of the data.
48
49#![no_std]
50
51#![cfg_attr(feature = "use-nightly-features", feature(extend_one))]
52
53use core::fmt::{self, Display};
54use core::ops::{Deref, DerefMut};
55use core::str::{self, Utf8Error};
56
57extern crate alloc;
58use alloc::vec::Vec;
59use alloc::boxed::Box;
60
61use tiny_vec::TinyVec;
62pub mod iter;
63
64const MAX_N_STACK_ELEMENTS: usize = tiny_vec::n_elements_for_stack::<u8>();
65
66/// A string that can store a small amount of bytes on the stack.
67pub struct TinyString<const N: usize = MAX_N_STACK_ELEMENTS>(TinyVec<u8, N>);
68
69impl<const N: usize> TinyString<N> {
70
71    /// Creates a new [TinyString]
72    #[inline]
73    pub const fn new() -> Self {
74        Self(TinyVec::new())
75    }
76
77    /// Creates a new [TinyString] with the given capacity
78    pub fn with_capacity(cap: usize) -> Self {
79        Self(TinyVec::with_capacity(cap))
80    }
81
82    /// Creates a new [TinyString] from the given utf8 buffer.
83    ///
84    /// # Errors
85    /// If the byte buffer contains invalid uft8
86    pub fn from_utf8(utf8: TinyVec<u8, N>) -> Result<Self,Utf8Error> {
87        str::from_utf8(utf8.as_slice())?;
88        Ok(Self(utf8))
89    }
90
91    /// Creates a new [TinyString] from the given utf8 buffer.
92    ///
93    /// # Safety
94    /// The caller must ensure that the given contains valid utf8
95    #[inline(always)]
96    pub const unsafe fn from_utf8_unchecked(utf8: TinyVec<u8, N>) -> Self {
97        Self(utf8)
98    }
99
100    /// Returns the number of elements inside this string
101    #[inline]
102    pub const fn len(&self) -> usize { self.0.len() }
103
104    /// Returns true if the string is empty
105    #[inline]
106    pub const fn is_empty(&self) -> bool { self.0.is_empty() }
107
108    /// Returns the allocated capacity for this string
109    #[inline]
110    pub const fn capacity(&self) -> usize { self.0.capacity() }
111
112    /// Returns a str slice
113    #[inline]
114    pub const fn as_str(&self) -> &str {
115        unsafe { str::from_utf8_unchecked(self.0.as_slice()) }
116    }
117
118    /// Returns a mutable str slice
119    #[inline]
120    pub const fn as_mut_str(&mut self) -> &mut str {
121        unsafe { str::from_utf8_unchecked_mut(self.0.as_mut_slice()) }
122    }
123
124    /// Returns the string as a byte slice
125    #[inline]
126    pub const fn as_bytes(&self) -> &[u8] {
127        self.0.as_slice()
128    }
129
130    /// Pushes a character into the string
131    pub fn push(&mut self, c: char) {
132        let len = c.len_utf8();
133        if len == 1 {
134            self.0.push(c as u8);
135        } else {
136            let mut buf = [0_u8; 4];
137            c.encode_utf8(&mut buf);
138            self.0.extend_from_slice(&buf[..len]);
139        }
140    }
141
142    /// Returns the last char of this string, if present
143    ///
144    /// # Example
145    /// ```
146    /// use tiny_str::TinyString;
147    ///
148    /// let mut s = TinyString::<10>::new();
149    ///
150    /// s.push_str("abcd");
151    ///
152    /// assert_eq!(s.pop(), Some('d'));
153    /// assert_eq!(s, "abc");
154    /// ```
155    pub fn pop(&mut self) -> Option<char> {
156        let c = self.chars().next_back()?;
157        let new_len = self.len() - c.len_utf8();
158        unsafe {
159            self.0.set_len(new_len);
160        }
161        Some(c)
162    }
163
164    /// Pushes a str slice into this string
165    #[inline]
166    pub fn push_str(&mut self, s: &str) {
167        self.0.extend_from_slice_copied(s.as_bytes());
168    }
169
170    /// Shrinks the capacity of this string to fit exactly it's length
171    #[inline]
172    pub fn shrink_to_fit(&mut self) {
173        self.0.shrink_to_fit();
174    }
175
176    /// Reserves space for, at least, n bytes
177    #[inline]
178    pub fn reserve(&mut self, n: usize) {
179        self.0.reserve(n);
180    }
181
182    /// Reserves space for exactly n more bytes
183    #[inline]
184    pub fn reserve_exact(&mut self, n: usize) {
185        self.0.reserve_exact(n);
186    }
187
188    /// Converts this TinyString into a boxed str
189    ///
190    /// # Example
191    /// ```
192    /// use tiny_str::TinyString;
193    ///
194    /// let mut s = TinyString::<10>::new();
195    /// s.push_str("abc");
196    ///
197    /// let b = s.into_boxed_str();
198    /// assert_eq!(&*b, "abc");
199    /// ```
200    pub fn into_boxed_str(self) -> Box<str> {
201        let b = self.0.into_boxed_slice();
202        unsafe { alloc::str::from_boxed_utf8_unchecked(b) }
203    }
204}
205
206impl<const N: usize> Default for TinyString<N> {
207    fn default() -> Self {
208        Self::new()
209    }
210}
211
212impl<const N: usize> Deref for TinyString<N> {
213    type Target = str;
214
215    fn deref(&self) -> &Self::Target {
216        self.as_str()
217    }
218}
219
220impl<const N: usize> DerefMut for TinyString<N> {
221    fn deref_mut(&mut self) -> &mut Self::Target {
222        self.as_mut_str()
223    }
224}
225
226impl<const N: usize> From<&str> for TinyString<N> {
227    fn from(value: &str) -> Self {
228        let mut s = Self::with_capacity(value.len());
229        s.push_str(value);
230        s
231    }
232}
233
234impl<const N: usize> TryFrom<&[u8]> for TinyString<N> {
235    type Error = Utf8Error;
236
237    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
238        str::from_utf8(value)?;
239        Ok(unsafe { Self::from_utf8_unchecked(TinyVec::from_slice_copied(value)) })
240    }
241}
242
243impl<const N: usize> TryFrom<TinyVec<u8, N>> for TinyString<N> {
244    type Error = Utf8Error;
245
246    fn try_from(value: TinyVec<u8, N>) -> Result<Self, Self::Error> {
247        Self::from_utf8(value)
248    }
249}
250
251impl<const N: usize> TryFrom<Vec<u8>> for TinyString<N> {
252    type Error = Utf8Error;
253
254    fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
255        str::from_utf8(value.as_slice())?;
256        Ok(unsafe { Self::from_utf8_unchecked(TinyVec::from_vec(value)) })
257    }
258}
259
260impl<const N: usize> From<TinyString<N>> for TinyVec<u8, N> {
261    fn from(value: TinyString<N>) -> Self {
262        value.0
263    }
264}
265
266impl<const N: usize> From<TinyString<N>> for Vec<u8> {
267    fn from(value: TinyString<N>) -> Self {
268        value.0.into_vec()
269    }
270}
271
272impl<const N: usize> From<TinyString<N>> for Box<str> {
273    fn from(value: TinyString<N>) -> Self {
274        value.into_boxed_str()
275    }
276}
277
278impl<const N: usize> FromIterator<char> for TinyString<N> {
279    fn from_iter<T: IntoIterator<Item = char>>(iter: T) -> Self {
280        let mut s = Self::new();
281        s.extend(iter);
282        s
283    }
284}
285
286impl<const N: usize> Extend<char> for TinyString<N> {
287    fn extend<T: IntoIterator<Item = char>>(&mut self, iter: T) {
288        let iter = iter.into_iter();
289        let cap = match iter.size_hint() {
290            (_, Some(n)) => n,
291            (n, _) => n,
292        };
293        self.reserve(cap);
294        for c in iter {
295            self.push(c);
296        }
297    }
298
299    #[cfg(feature = "use-nightly-features")]
300    fn extend_one(&mut self, item: char) {
301        self.push(item);
302    }
303}
304
305impl<const N: usize, S> PartialEq<S> for TinyString<N>
306where
307    S: AsRef<[u8]>
308{
309    fn eq(&self, other: &S) -> bool {
310        self.as_bytes() == other.as_ref()
311    }
312}
313
314impl<const N: usize> Eq for TinyString<N> { }
315
316impl<const N: usize> AsRef<[u8]> for TinyString<N> {
317    fn as_ref(&self) -> &[u8] {
318        self.as_bytes()
319    }
320}
321
322impl<const N: usize> AsRef<str> for TinyString<N> {
323    fn as_ref(&self) -> &str {
324        self.as_str()
325    }
326}
327
328impl<const N: usize> AsMut<str> for TinyString<N> {
329    fn as_mut(&mut self) -> &mut str {
330        self.as_mut_str()
331    }
332}
333
334impl<const N: usize> fmt::Debug for TinyString<N> {
335    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
336        write!(f, "{:?}", self.bytes())
337    }
338}
339
340impl<const N: usize> Display for TinyString<N> {
341    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
342        write!(f, "{}", self.as_str())
343    }
344}
345
346#[cfg(test)]
347mod test;