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