Skip to main content

planck_noalloc/
smallbytestr.rs

1//! A byte string backed by [`SmallVec`].
2//!
3//! [`SmallByteStr<N>`] stores up to `N` bytes inline on the stack. When the buffer grows
4//! beyond `N` bytes, it spills to a heap-allocated buffer. Unlike [`SmallStr`],
5//! no UTF-8 validity is enforced.
6//!
7//! # Examples
8//!
9//! ```
10//! use planck_noalloc::smallbytestr::SmallByteStr;
11//!
12//! let mut bs = SmallByteStr::<8>::new();
13//! bs.push(0x48); // 'H'
14//! bs.push(0x69); // 'i'
15//! assert_eq!(bs.as_bytes(), b"Hi");
16//! assert!(bs.is_inline());
17//! ```
18
19use alloc::vec::Vec;
20
21use crate::smallstr::SmallStr;
22use crate::smallvec::SmallVec;
23
24/// A byte string that stores up to `N` bytes inline, spilling to the heap when exceeded.
25///
26/// This is a thin wrapper around `SmallVec<u8, N>` with a byte-string-oriented API.
27/// No UTF-8 validity is enforced.
28#[derive(Clone, Default)]
29pub struct SmallByteStr<const N: usize> {
30    buf: SmallVec<u8, N>,
31}
32
33impl<const N: usize> SmallByteStr<N> {
34    /// Creates a new, empty `SmallByteStr`.
35    #[must_use]
36    pub const fn new() -> Self {
37        Self {
38            buf: SmallVec::new(),
39        }
40    }
41
42    /// Creates a new `SmallByteStr` with at least the specified byte capacity.
43    #[must_use]
44    pub fn with_capacity(cap: usize) -> Self {
45        Self {
46            buf: SmallVec::with_capacity(cap),
47        }
48    }
49
50    /// Creates a `SmallByteStr` from a byte slice.
51    #[must_use]
52    pub fn from_bytes(bytes: &[u8]) -> Self {
53        let mut bs = Self::with_capacity(bytes.len());
54        bs.buf.extend(bytes.iter().copied());
55        bs
56    }
57
58    /// Appends a slice of bytes.
59    pub fn push_bytes(&mut self, bytes: &[u8]) {
60        self.buf.extend(bytes.iter().copied());
61    }
62
63    /// Appends a single byte.
64    pub fn push(&mut self, byte: u8) {
65        self.buf.push(byte);
66    }
67
68    /// Removes and returns the last byte, or `None` if empty.
69    #[must_use]
70    pub fn pop(&mut self) -> Option<u8> {
71        self.buf.pop()
72    }
73
74    /// Removes all content.
75    pub fn clear(&mut self) {
76        self.buf.clear();
77    }
78
79    /// Returns the byte length.
80    #[must_use]
81    pub fn len(&self) -> usize {
82        self.buf.len()
83    }
84
85    /// Returns `true` if empty.
86    #[must_use]
87    pub fn is_empty(&self) -> bool {
88        self.buf.is_empty()
89    }
90
91    /// Returns the byte capacity.
92    #[must_use]
93    pub fn capacity(&self) -> usize {
94        self.buf.capacity()
95    }
96
97    /// Returns `true` if the data is stored inline.
98    #[must_use]
99    pub fn is_inline(&self) -> bool {
100        self.buf.is_inline()
101    }
102
103    /// Reserves capacity for at least `additional` more bytes.
104    pub fn reserve(&mut self, additional: usize) {
105        self.buf.reserve(additional);
106    }
107
108    /// Shrinks the backing allocation to fit. May move data back inline.
109    pub fn shrink_to_fit(&mut self) {
110        self.buf.shrink_to_fit();
111    }
112
113    /// Forces the data to the heap if currently inline.
114    pub fn spill(&mut self) {
115        self.buf.spill();
116    }
117
118    /// Returns the content as a byte slice.
119    #[must_use]
120    pub fn as_bytes(&self) -> &[u8] {
121        self.buf.as_slice()
122    }
123
124    /// Returns the content as a mutable byte slice.
125    #[must_use]
126    pub fn as_bytes_mut(&mut self) -> &mut [u8] {
127        self.buf.as_mut_slice()
128    }
129
130    /// Consumes the byte string and returns the underlying `SmallVec<u8, N>`.
131    #[must_use]
132    pub fn into_bytes(self) -> SmallVec<u8, N> {
133        self.buf
134    }
135
136    /// Consumes the byte string and returns a heap-allocated `Vec<u8>`.
137    #[must_use]
138    pub fn into_vec(self) -> Vec<u8> {
139        self.buf.into_vec()
140    }
141
142    /// Truncates the byte string to `new_len` bytes.
143    ///
144    /// If `new_len >= len`, this is a no-op.
145    pub fn truncate(&mut self, new_len: usize) {
146        self.buf.truncate(new_len);
147    }
148
149    /// Attempts to convert this byte string into a UTF-8 [`SmallStr`].
150    ///
151    /// # Errors
152    ///
153    /// Returns `Err(self)` if the bytes are not valid UTF-8.
154    pub fn into_small_str(self) -> Result<SmallStr<N>, Self> {
155        if core::str::from_utf8(self.buf.as_slice()).is_ok() {
156            // SAFETY: We just verified the bytes are valid UTF-8.
157            Ok(unsafe { SmallStr::from_small_vec_unchecked(self.buf) })
158        } else {
159            Err(self)
160        }
161    }
162}
163
164impl<const N: usize> core::fmt::Debug for SmallByteStr<N> {
165    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
166        // Print as byte slice for clarity
167        write!(f, "SmallByteStr({:?})", self.buf.as_slice())
168    }
169}
170
171impl<const N: usize> core::ops::Deref for SmallByteStr<N> {
172    type Target = [u8];
173
174    fn deref(&self) -> &[u8] {
175        self.as_bytes()
176    }
177}
178
179impl<const N: usize> core::ops::DerefMut for SmallByteStr<N> {
180    fn deref_mut(&mut self) -> &mut [u8] {
181        self.as_bytes_mut()
182    }
183}
184
185impl<const N: usize> PartialEq for SmallByteStr<N> {
186    fn eq(&self, other: &Self) -> bool {
187        self.as_bytes() == other.as_bytes()
188    }
189}
190
191impl<const N: usize> Eq for SmallByteStr<N> {}
192
193impl<const N: usize> PartialOrd for SmallByteStr<N> {
194    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
195        Some(self.cmp(other))
196    }
197}
198
199impl<const N: usize> Ord for SmallByteStr<N> {
200    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
201        self.as_bytes().cmp(other.as_bytes())
202    }
203}
204
205impl<const N: usize> core::hash::Hash for SmallByteStr<N> {
206    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
207        self.as_bytes().hash(state);
208    }
209}
210
211impl<const N: usize> From<&[u8]> for SmallByteStr<N> {
212    fn from(bytes: &[u8]) -> Self {
213        Self::from_bytes(bytes)
214    }
215}
216
217impl<const N: usize> From<Vec<u8>> for SmallByteStr<N> {
218    fn from(v: Vec<u8>) -> Self {
219        Self {
220            buf: SmallVec::from(v),
221        }
222    }
223}
224
225impl<const N: usize> From<&str> for SmallByteStr<N> {
226    fn from(s: &str) -> Self {
227        Self::from_bytes(s.as_bytes())
228    }
229}
230
231impl<const N: usize> Extend<u8> for SmallByteStr<N> {
232    fn extend<I: IntoIterator<Item = u8>>(&mut self, iter: I) {
233        self.buf.extend(iter);
234    }
235}
236
237impl<const N: usize> FromIterator<u8> for SmallByteStr<N> {
238    fn from_iter<I: IntoIterator<Item = u8>>(iter: I) -> Self {
239        Self {
240            buf: SmallVec::from_iter(iter),
241        }
242    }
243}
244
245impl<const N: usize> AsRef<[u8]> for SmallByteStr<N> {
246    fn as_ref(&self) -> &[u8] {
247        self.as_bytes()
248    }
249}
250
251impl<const N: usize> core::borrow::Borrow<[u8]> for SmallByteStr<N> {
252    fn borrow(&self) -> &[u8] {
253        self.as_bytes()
254    }
255}
256
257#[cfg(test)]
258mod tests {
259    extern crate alloc;
260    use alloc::vec;
261
262    use super::*;
263
264    #[test]
265    fn new_is_empty() {
266        let bs = SmallByteStr::<8>::new();
267        assert!(bs.is_empty());
268        assert_eq!(bs.len(), 0);
269    }
270
271    #[test]
272    fn push_and_pop() {
273        let mut bs = SmallByteStr::<8>::new();
274        bs.push(0x41);
275        bs.push(0x42);
276        assert_eq!(bs.as_bytes(), b"AB");
277        assert_eq!(bs.pop(), Some(0x42));
278        assert_eq!(bs.pop(), Some(0x41));
279        assert_eq!(bs.pop(), None);
280    }
281
282    #[test]
283    fn from_bytes() {
284        let bs = SmallByteStr::<8>::from_bytes(b"hello");
285        assert_eq!(bs.as_bytes(), b"hello");
286        assert!(bs.is_inline());
287    }
288
289    #[test]
290    fn push_bytes() {
291        let mut bs = SmallByteStr::<16>::new();
292        bs.push_bytes(b"hello");
293        bs.push_bytes(b" world");
294        assert_eq!(bs.as_bytes(), b"hello world");
295    }
296
297    #[test]
298    fn spills_to_heap() {
299        let mut bs = SmallByteStr::<2>::new();
300        bs.push(1);
301        bs.push(2);
302        assert!(bs.is_inline());
303        bs.push(3);
304        assert!(!bs.is_inline());
305        assert_eq!(bs.as_bytes(), &[1, 2, 3]);
306    }
307
308    #[test]
309    fn clear() {
310        let mut bs = SmallByteStr::<8>::from_bytes(b"hello");
311        bs.clear();
312        assert!(bs.is_empty());
313    }
314
315    #[test]
316    fn truncate() {
317        let mut bs = SmallByteStr::<8>::from_bytes(b"hello");
318        bs.truncate(3);
319        assert_eq!(bs.as_bytes(), b"hel");
320    }
321
322    #[test]
323    fn into_vec() {
324        let bs = SmallByteStr::<8>::from_bytes(b"hi");
325        let v = bs.into_vec();
326        assert_eq!(v, vec![b'h', b'i']);
327    }
328
329    #[test]
330    fn into_bytes() {
331        let bs = SmallByteStr::<8>::from_bytes(b"hi");
332        let sv = bs.into_bytes();
333        assert_eq!(sv.as_slice(), b"hi");
334    }
335
336    #[test]
337    fn into_small_str_valid() {
338        let bs = SmallByteStr::<8>::from_bytes(b"hello");
339        let s = bs.into_small_str().unwrap();
340        assert_eq!(s.as_str(), "hello");
341    }
342
343    #[test]
344    fn into_small_str_invalid() {
345        let bs = SmallByteStr::<8>::from_bytes(&[0xFF, 0xFE]);
346        let err = bs.into_small_str().unwrap_err();
347        assert_eq!(err.as_bytes(), &[0xFF, 0xFE]);
348    }
349
350    #[test]
351    fn from_slice() {
352        let bs: SmallByteStr<8> = b"hello".as_slice().into();
353        assert_eq!(bs.as_bytes(), b"hello");
354    }
355
356    #[test]
357    fn from_vec() {
358        let bs: SmallByteStr<8> = SmallByteStr::from(vec![1, 2, 3]);
359        assert_eq!(bs.as_bytes(), &[1, 2, 3]);
360    }
361
362    #[test]
363    fn from_str() {
364        let bs: SmallByteStr<8> = SmallByteStr::from("hi");
365        assert_eq!(bs.as_bytes(), b"hi");
366    }
367
368    #[test]
369    fn extend_and_from_iter() {
370        let bs: SmallByteStr<4> = (0..5u8).collect();
371        assert_eq!(bs.as_bytes(), &[0, 1, 2, 3, 4]);
372    }
373
374    #[test]
375    fn eq_and_ord() {
376        let a = SmallByteStr::<8>::from_bytes(b"abc");
377        let b = SmallByteStr::<8>::from_bytes(b"abc");
378        let c = SmallByteStr::<8>::from_bytes(b"abd");
379        assert_eq!(a, b);
380        assert!(a < c);
381    }
382
383    #[test]
384    fn debug_format() {
385        let bs = SmallByteStr::<8>::from_bytes(b"hi");
386        let s = alloc::format!("{bs:?}");
387        assert!(s.contains("SmallByteStr"));
388    }
389
390    #[test]
391    fn deref_and_deref_mut() {
392        let mut bs = SmallByteStr::<8>::from_bytes(b"hi");
393        assert_eq!(bs.len(), 2);
394        assert!(bs.contains(&b'h'));
395        bs[0] = b'H';
396        assert_eq!(bs.as_bytes(), b"Hi");
397    }
398
399    #[test]
400    fn as_bytes_mut() {
401        let mut bs = SmallByteStr::<8>::from_bytes(b"hello");
402        bs.as_bytes_mut()[0] = b'H';
403        assert_eq!(bs.as_bytes(), b"Hello");
404    }
405
406    #[test]
407    fn clone() {
408        let bs = SmallByteStr::<8>::from_bytes(b"hello");
409        let bs2 = bs.clone();
410        assert_eq!(bs.as_bytes(), bs2.as_bytes());
411    }
412
413    #[test]
414    fn reserve_and_shrink() {
415        let mut bs = SmallByteStr::<8>::from_bytes(b"hi");
416        bs.reserve(100);
417        assert!(!bs.is_inline());
418        bs.shrink_to_fit();
419        assert!(bs.is_inline());
420        assert_eq!(bs.as_bytes(), b"hi");
421    }
422
423    #[test]
424    fn with_capacity() {
425        let bs = SmallByteStr::<4>::with_capacity(3);
426        assert!(bs.is_inline());
427        let bs = SmallByteStr::<4>::with_capacity(10);
428        assert!(!bs.is_inline());
429    }
430
431    #[test]
432    fn as_ref_and_borrow() {
433        let bs = SmallByteStr::<8>::from_bytes(b"hi");
434        let r: &[u8] = bs.as_ref();
435        assert_eq!(r, b"hi");
436        let b: &[u8] = core::borrow::Borrow::borrow(&bs);
437        assert_eq!(b, b"hi");
438    }
439}