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::{Bound, Deref, DerefMut, Range, RangeBounds};
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> {
70 buf: TinyVec<u8, N>,
71}
72
73impl<const N: usize> TinyString<N> {
74 fn slice_range<R>(&self, range: R, len: usize) -> Range<usize>
75 where
76 R: RangeBounds<usize>
77 {
78 let start = match range.start_bound() {
79 Bound::Included(n) => *n,
80 Bound::Excluded(n) => *n + 1,
81 Bound::Unbounded => 0,
82 };
83
84 let end = match range.end_bound() {
85 Bound::Included(n) => *n + 1,
86 Bound::Excluded(n) => *n,
87 Bound::Unbounded => len,
88 };
89
90 assert!(start <= end);
91 assert!(end <= len);
92 assert!(self.is_char_boundary(start));
93 assert!(self.is_char_boundary(end));
94
95 Range { start, end }
96 }
97}
98
99impl<const N: usize> TinyString<N> {
100
101 /// Creates a new [TinyString]
102 #[inline]
103 pub const fn new() -> Self {
104 Self { buf: TinyVec::new() }
105 }
106
107 /// Creates a new [TinyString] with the given capacity
108 pub fn with_capacity(cap: usize) -> Self {
109 Self { buf: TinyVec::with_capacity(cap) }
110 }
111
112 /// Creates a new [TinyString] from the given utf8 buffer.
113 ///
114 /// # Errors
115 /// If the byte buffer contains invalid uft8
116 pub fn from_utf8(utf8: TinyVec<u8, N>) -> Result<Self,Utf8Error> {
117 str::from_utf8(utf8.as_slice())?;
118 Ok(Self { buf: utf8 })
119 }
120
121 /// Creates a new [TinyString] from the given utf8 buffer.
122 ///
123 /// # Safety
124 /// The caller must ensure that the given contains valid utf8
125 #[inline(always)]
126 pub const unsafe fn from_utf8_unchecked(utf8: TinyVec<u8, N>) -> Self {
127 Self { buf: utf8 }
128 }
129
130 /// Creates a new `TinyString` by repeating the given `slice` `n` times.
131 ///
132 /// # Panics
133 /// If the capacity requires overflows `isize::MAX`
134 ///
135 /// # Example
136 /// ```
137 /// use tiny_str::TinyString;
138 /// let s = TinyString::<10>::repeat("abc", 5);
139 /// assert_eq!(s.as_str(), "abcabcabcabcabc");
140 /// ```
141 pub fn repeat(slice: &str, n: usize) -> Self {
142 let len = slice.len() * n;
143 let mut s = Self::with_capacity(len);
144 let bytes = slice.as_bytes();
145 for _ in 0..n {
146 s.buf.extend_from_slice_copied(bytes);
147 }
148 s
149 }
150
151 /// Returns the number of elements inside this string
152 #[inline]
153 pub const fn len(&self) -> usize { self.buf.len() }
154
155 /// Returns true if the string is empty
156 #[inline]
157 pub const fn is_empty(&self) -> bool { self.buf.is_empty() }
158
159 /// Returns the allocated capacity for this string
160 #[inline]
161 pub const fn capacity(&self) -> usize { self.buf.capacity() }
162
163 /// Returns a str slice
164 #[inline]
165 pub const fn as_str(&self) -> &str {
166 unsafe { str::from_utf8_unchecked(self.buf.as_slice()) }
167 }
168
169 /// Returns a mutable str slice
170 #[inline]
171 pub const fn as_mut_str(&mut self) -> &mut str {
172 unsafe { str::from_utf8_unchecked_mut(self.buf.as_mut_slice()) }
173 }
174
175 /// Returns a const pointer to the buffer
176 ///
177 /// This method shadows [str::as_ptr], to avoid a deref
178 #[inline]
179 pub const fn as_ptr(&self) -> *const u8 {
180 self.buf.as_ptr()
181 }
182
183 /// Returns a mutable pointer to the buffer
184 ///
185 /// This method shadows [str::as_mut_ptr], to avoid a deref
186 #[inline]
187 pub const fn as_mut_ptr(&mut self) -> *mut u8 {
188 self.buf.as_mut_ptr()
189 }
190
191 /// Returns the string as a byte slice
192 #[inline]
193 pub const fn as_bytes(&self) -> &[u8] {
194 self.buf.as_slice()
195 }
196
197 /// Pushes a character into the string
198 pub fn push(&mut self, c: char) {
199 let len = c.len_utf8();
200 if len == 1 {
201 self.buf.push(c as u8);
202 } else {
203 let mut buf = [0_u8; 4];
204 c.encode_utf8(&mut buf);
205 self.buf.extend_from_slice(&buf[..len]);
206 }
207 }
208
209 /// Returns the last char of this string, if present
210 ///
211 /// # Example
212 /// ```
213 /// use tiny_str::TinyString;
214 ///
215 /// let mut s = TinyString::<10>::new();
216 ///
217 /// s.push_str("abcd");
218 ///
219 /// assert_eq!(s.pop(), Some('d'));
220 /// assert_eq!(s, "abc");
221 /// ```
222 pub fn pop(&mut self) -> Option<char> {
223 let c = self.chars().next_back()?;
224 let new_len = self.len() - c.len_utf8();
225 unsafe {
226 self.buf.set_len(new_len);
227 }
228 Some(c)
229 }
230
231 /// Pushes a str slice into this string
232 #[inline]
233 pub fn push_str(&mut self, s: &str) {
234 self.buf.extend_from_slice_copied(s.as_bytes());
235 }
236
237 /// Shrinks the capacity of this string to fit exactly it's length
238 #[inline]
239 pub fn shrink_to_fit(&mut self) {
240 self.buf.shrink_to_fit();
241 }
242
243 /// Clears the string
244 ///
245 /// # Example
246 /// ```
247 /// use tiny_str::TinyString;
248 ///
249 /// let mut s: TinyString<5> = TinyString::from("Hello");
250 /// s.clear();
251 ///
252 /// assert!(s.is_empty());
253 /// assert_eq!(s.as_str(), "");
254 /// ```
255 #[inline]
256 pub fn clear(&mut self) {
257 self.buf.clear();
258 }
259
260 /// Reserves space for, at least, n bytes
261 #[inline]
262 pub fn reserve(&mut self, n: usize) {
263 self.buf.reserve(n);
264 }
265
266 /// Reserves space for exactly n more bytes
267 #[inline]
268 pub fn reserve_exact(&mut self, n: usize) {
269 self.buf.reserve_exact(n);
270 }
271
272 /// Converts this TinyString into a boxed str
273 ///
274 /// # Example
275 /// ```
276 /// use tiny_str::TinyString;
277 ///
278 /// let mut s = TinyString::<10>::new();
279 /// s.push_str("abc");
280 ///
281 /// let b = s.into_boxed_str();
282 /// assert_eq!(&*b, "abc");
283 /// ```
284 pub fn into_boxed_str(self) -> Box<str> {
285 let b = self.buf.into_boxed_slice();
286 unsafe { alloc::str::from_boxed_utf8_unchecked(b) }
287 }
288
289 /// Copies the slice from the given range to the back
290 /// of this string.
291 ///
292 /// # Panics
293 /// - If the range is invalid for [0, self.len)
294 /// - If either the start or the end of the range fall
295 /// outside a char boundary
296 ///
297 /// # Example
298 /// ```
299 /// use tiny_str::TinyString;
300 ///
301 /// let mut s = TinyString::<10>::from("abcdefg");
302 ///
303 /// s.extend_from_within(3..=5);
304 ///
305 /// assert_eq!(s, "abcdefgdef");
306 /// ```
307 pub fn extend_from_within<R>(&mut self, range: R)
308 where
309 R: RangeBounds<usize>
310 {
311 let Range { start, end } = self.slice_range(range, self.len());
312 self.buf.extend_from_within_copied(start..end);
313 }
314
315 /// Consumes and leaks the `TinyString`, returning a mutable reference to the contents,
316 /// `&'a mut str`.
317 ///
318 /// This method shrinks the buffer, and moves it to the heap in case it lived
319 /// on the stack.
320 ///
321 /// This function is mainly useful for data that lives for the remainder of
322 /// the program's life. Dropping the returned reference will cause a memory
323 /// leak.
324 ///
325 /// # Example
326 /// ```
327 /// let x = tiny_str::TinyString::<10>::from("ABCDEFG");
328 ///
329 /// let static_ref: &'static mut str = x.leak();
330 /// static_ref.make_ascii_lowercase();
331 ///
332 /// assert_eq!(static_ref, "abcdefg");
333 /// # // FIXME(https://github.com/rust-lang/miri/issues/3670):
334 /// # // use -Zmiri-disable-leak-check instead of unleaking in tests meant to leak.
335 /// # drop(unsafe{Box::from_raw(static_ref)})
336 /// ```
337 pub fn leak<'a>(mut self) -> &'a mut str {
338 self.buf.move_to_heap_exact();
339 self.buf.shrink_to_fit_heap_only();
340 unsafe {
341 let bytes = self.buf.leak();
342 str::from_utf8_unchecked_mut(bytes)
343 }
344 }
345
346 /// Splits the string into two at the given byte index.
347 ///
348 /// Returns a newly allocated `String`. `self` contains bytes `[0, at)`, and
349 /// the returned `String` contains bytes `[at, len)`. `at` must be on the
350 /// boundary of a UTF-8 code point.
351 ///
352 /// Note that the capacity of `self` does not change.
353 ///
354 /// # Panics
355 ///
356 /// Panics if `at` is not on a `UTF-8` code point boundary, or if it is beyond the last
357 /// code point of the string.
358 ///
359 /// # Examples
360 /// ```
361 /// let mut hello = tiny_str::TinyString::<8>::from("Hello, World!");
362 /// let world = hello.split_off(7);
363 /// assert_eq!(hello, "Hello, ");
364 /// assert_eq!(world, "World!");
365 /// ```
366 #[inline]
367 #[must_use = "use `.truncate()` if you don't need the other half"]
368 pub fn split_off(&mut self, at: usize) -> TinyString<N> {
369 assert!(self.is_char_boundary(at));
370 let other = self.buf.split_off(at);
371 unsafe { TinyString::from_utf8_unchecked(other) }
372 }
373
374 /// Shortens this `TinyString` to the specified length.
375 ///
376 /// If `new_len` is greater than or equal to the string's current length, this has no
377 /// effect.
378 ///
379 /// Note that this method has no effect on the allocated capacity
380 /// of the string
381 ///
382 /// # Panics
383 ///
384 /// Panics if `new_len` does not lie on a [`char`] boundary.
385 ///
386 /// # Example
387 /// ```
388 /// let mut s = tiny_str::TinyString::<6>::from("hello");
389 ///
390 /// s.truncate(2);
391 ///
392 /// assert_eq!(s, "he");
393 /// ```
394 pub fn truncate(&mut self, new_len: usize) {
395 assert!(self.is_char_boundary(new_len));
396 self.buf.truncate(new_len);
397 }
398
399 /// Inserts a character into this `TinyString` at a byte position.
400 ///
401 /// This is an *O*(*n*) operation as it requires copying every element in the
402 /// buffer.
403 ///
404 /// # Panics
405 ///
406 /// Panics if `index` is larger than the `TinyString`'s length, or if it does not
407 /// lie on a [`char`] boundary.
408 ///
409 /// # Example
410 /// ```
411 /// let mut s = tiny_str::TinyString::<10>::from("Hello world :)");
412 ///
413 /// s.insert(5, '@');
414 ///
415 /// assert_eq!(s, "Hello@ world :)");
416 /// ```
417 pub fn insert(&mut self, index: usize, ch: char) {
418 assert!(self.is_char_boundary(index));
419 let mut buf = [0; 4];
420 ch.encode_utf8(&mut buf);
421 let len = ch.len_utf8();
422 self.buf.insert_slice(index, &buf[..len]).unwrap_or_else(|_| {
423 unreachable!("We've checked the index in the assertion above")
424 })
425 }
426
427 /// Inserts a string slice into this `TinyString` at a byte position.
428 ///
429 /// This is an *O*(*n*) operation as it requires copying every element in the
430 /// buffer.
431 ///
432 /// # Panics
433 ///
434 /// Panics if `index` is larger than the `TinyString`'s length, or if it does not
435 /// lie on a [`char`] boundary.
436 ///
437 /// # Example
438 /// ```
439 /// let mut s = tiny_str::TinyString::<8>::from("Heworld");
440 ///
441 /// s.insert_str(2, "llo ");
442 ///
443 /// assert_eq!("Hello world", s);
444 /// ```
445 pub fn insert_str(&mut self, index: usize, s: &str) {
446 assert!(self.is_char_boundary(index));
447 self.buf.insert_slice(index, s.as_bytes()).unwrap_or_else(|_| {
448 unreachable!("We've checked the index in the assertion above")
449 })
450 }
451}
452
453impl<const N: usize> Default for TinyString<N> {
454 fn default() -> Self {
455 Self::new()
456 }
457}
458
459impl<const N: usize> Deref for TinyString<N> {
460 type Target = str;
461
462 fn deref(&self) -> &Self::Target {
463 self.as_str()
464 }
465}
466
467impl<const N: usize> DerefMut for TinyString<N> {
468 fn deref_mut(&mut self) -> &mut Self::Target {
469 self.as_mut_str()
470 }
471}
472
473impl<const N: usize> From<&str> for TinyString<N> {
474 fn from(value: &str) -> Self {
475 let mut s = Self::with_capacity(value.len());
476 s.push_str(value);
477 s
478 }
479}
480
481impl<const N: usize> TryFrom<&[u8]> for TinyString<N> {
482 type Error = Utf8Error;
483
484 fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
485 str::from_utf8(value)?;
486 Ok(unsafe { Self::from_utf8_unchecked(TinyVec::from_slice_copied(value)) })
487 }
488}
489
490impl<const N: usize> TryFrom<TinyVec<u8, N>> for TinyString<N> {
491 type Error = Utf8Error;
492
493 fn try_from(value: TinyVec<u8, N>) -> Result<Self, Self::Error> {
494 Self::from_utf8(value)
495 }
496}
497
498impl<const N: usize> TryFrom<Vec<u8>> for TinyString<N> {
499 type Error = Utf8Error;
500
501 fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
502 str::from_utf8(value.as_slice())?;
503 Ok(unsafe { Self::from_utf8_unchecked(TinyVec::from_vec(value)) })
504 }
505}
506
507impl<const N: usize> From<TinyString<N>> for TinyVec<u8, N> {
508 fn from(value: TinyString<N>) -> Self {
509 value.buf
510 }
511}
512
513impl<const N: usize> From<TinyString<N>> for Vec<u8> {
514 fn from(value: TinyString<N>) -> Self {
515 value.buf.into_vec()
516 }
517}
518
519impl<const N: usize> From<TinyString<N>> for Box<str> {
520 fn from(value: TinyString<N>) -> Self {
521 value.into_boxed_str()
522 }
523}
524
525impl<const N: usize> FromIterator<char> for TinyString<N> {
526 fn from_iter<T: IntoIterator<Item = char>>(iter: T) -> Self {
527 let mut s = Self::new();
528 s.extend(iter);
529 s
530 }
531}
532
533impl<const N: usize> Extend<char> for TinyString<N> {
534 fn extend<T: IntoIterator<Item = char>>(&mut self, iter: T) {
535 let iter = iter.into_iter();
536 let cap = match iter.size_hint() {
537 (_, Some(n)) => n,
538 (n, _) => n,
539 };
540 self.reserve(cap);
541 for c in iter {
542 self.push(c);
543 }
544 }
545
546 #[cfg(feature = "use-nightly-features")]
547 fn extend_one(&mut self, item: char) {
548 self.push(item);
549 }
550}
551
552impl<const N: usize, S> PartialEq<S> for TinyString<N>
553where
554 S: AsRef<str>,
555{
556 fn eq(&self, other: &S) -> bool {
557 self.as_str() == other.as_ref()
558 }
559}
560
561impl<const N: usize> PartialEq<TinyString<N>> for &str {
562 fn eq(&self, other: &TinyString<N>) -> bool {
563 self.as_bytes() == other.as_bytes()
564 }
565}
566
567impl<const N: usize> Eq for TinyString<N> { }
568
569impl<const N: usize> AsRef<[u8]> for TinyString<N> {
570 fn as_ref(&self) -> &[u8] {
571 self.as_bytes()
572 }
573}
574
575impl<const N: usize> AsRef<str> for TinyString<N> {
576 fn as_ref(&self) -> &str {
577 self.as_str()
578 }
579}
580
581impl<const N: usize> AsMut<str> for TinyString<N> {
582 fn as_mut(&mut self) -> &mut str {
583 self.as_mut_str()
584 }
585}
586
587impl<const N: usize> fmt::Debug for TinyString<N> {
588 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
589 write!(f, "{:?}", self.bytes())
590 }
591}
592
593impl<const N: usize> Display for TinyString<N> {
594 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
595 write!(f, "{}", self.as_str())
596 }
597}
598
599impl<const N: usize> FromStr for TinyString<N> {
600 type Err = core::convert::Infallible;
601
602 fn from_str(s: &str) -> Result<Self, Self::Err> {
603 Ok(Self::from(s))
604 }
605}
606
607#[cfg(test)]
608mod test;