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 #[inline]
61 pub const fn new() -> Self {
62 Self(TinyVec::new())
63 }
64
65 pub fn with_capacity(cap: usize) -> Self {
66 Self(TinyVec::with_capacity(cap))
67
68 }
69
70 pub fn from_utf8(utf8: Vec<u8>) -> Result<Self,Utf8Error> {
71 str::from_utf8(&utf8)?;
72 Ok(Self(utf8.into()))
73 }
74
75 /// # Safety
76 /// The caller must ensure that the given buffer is valid utf8
77 #[inline(always)]
78 pub const unsafe fn from_utf8_unchecked(utf8: TinyVec<u8, N>) -> Self {
79 Self(utf8)
80 }
81
82 pub fn push(&mut self, c: char) {
83 let len = c.len_utf8();
84 let mut buf = [0_u8; 4];
85 c.encode_utf8(&mut buf);
86 self.0.push_slice(&buf[..len]);
87 }
88
89 pub fn push_str(&mut self, s: &str) {
90 self.0.push_slice(s.as_bytes());
91 }
92
93 pub fn shrink_to_fit(&mut self) {
94 self.0.shrink_to_fit();
95 }
96
97 pub const fn capacity(&self) -> usize { self.0.capacity() }
98}
99
100impl<const N: usize> Default for TinyString<N> {
101 fn default() -> Self {
102 Self::new()
103 }
104}
105
106impl<const N: usize> Deref for TinyString<N> {
107 type Target = str;
108
109 fn deref(&self) -> &Self::Target {
110 unsafe {
111 str::from_utf8_unchecked(&self.0)
112 }
113 }
114}
115
116impl<const N: usize> DerefMut for TinyString<N> {
117 fn deref_mut(&mut self) -> &mut Self::Target {
118 unsafe {
119 str::from_utf8_unchecked_mut(&mut self.0)
120 }
121 }
122}
123
124impl<S, const N: usize> From<S> for TinyString<N>
125where
126 S: AsRef<str>,
127{
128 fn from(value: S) -> Self {
129 let value = value.as_ref();
130 let mut s = Self::with_capacity(value.len());
131 s.push_str(value);
132 s
133 }
134}
135
136#[cfg(test)]
137mod test;