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