Skip to main content

kevy_bytes/
lib.rs

1//! `SmallBytes` — a 24-byte small-byte-string with inline-SSO optimization.
2//!
3//! ```
4//! use kevy_bytes::SmallBytes;
5//!
6//! // Up to 23 bytes live in the value itself — no allocation, and the
7//! // whole string fits in one 24-byte slot.
8//! let short = SmallBytes::from_slice(b"user:1");
9//! assert_eq!(short.as_slice(), b"user:1");
10//! assert_eq!(short.heap_bytes(), 0);
11//!
12//! // Past the inline capacity it spills to the heap, and says so.
13//! let long = SmallBytes::from_slice(&[b'x'; 64]);
14//! assert_eq!(long.len(), 64);
15//! assert!(long.heap_bytes() >= 64);
16//! ```
17//!
18//! Layout (**little-endian only**): a union of two 24-byte variants, distinguished
19//! by the byte at offset 23:
20//!
21//! - **Inline**: `[u8; 23]` data, then `u8` tag holding the inline length
22//!   (0..=22). The whole string lives in the value, no allocation.
23//! - **Heap (64-bit)**: `NonNull<u8>` ptr (8) + `usize` len (8) + `usize`
24//!   cap_and_tag (8). The high byte of `cap_and_tag` overlaps byte 23 of
25//!   the union and is fixed at `0xFF` (> 22) as the heap discriminator. The
26//!   low 56 bits hold the heap capacity (up to 72 PB).
27//! - **Heap (32-bit)**: `NonNull<u8>` ptr (4) + `u32` len (4) + `u32`
28//!   cap (4) + 11-byte pad, then `u8` tag fixed at `0xFF`. Same 24-byte
29//!   total, same discriminator byte at offset 23 — pointer / len fields
30//!   are 32-bit-native so a `wasm32-unknown-unknown` build picks up the
31//!   right size without shifting a `usize` past its bit width.
32//!
33//! The 64-bit layout is the one the kevy server runs on, and is locked
34//! against perf-affecting changes (cfg-gated 32-bit alternative lives
35//! alongside it without touching any 64-bit code path).
36//!
37//! This lets us store every byte string up to 23 bytes — covering the vast
38//! majority of Redis-style values — without any pointer-chase, while keeping
39//! `size_of::<SmallBytes>() == 24` (same as `Vec<u8>`). Used by `kevy-store`
40//! to make `Value::Str(SmallBytes)` fit alongside the boxed collection
41//! variants and keep `Entry` at 48 B.
42
43#![warn(missing_docs)]
44#![cfg_attr(not(feature = "std"), no_std)]
45
46extern crate alloc;
47
48#[cfg(target_endian = "big")]
49compile_error!("kevy-bytes requires little-endian: heap-tag byte overlaps inline length byte");
50
51mod eq;
52mod find_crlf;
53mod traits;
54
55mod heap;
56pub(crate) use heap::{Heap, INLINE_CAP, INLINE_LEN_MAX, Inline};
57
58pub use find_crlf::find_crlf;
59
60use alloc::alloc::{Layout, alloc, dealloc, handle_alloc_error};
61use alloc::vec::Vec;
62use core::mem::{self, ManuallyDrop};
63use core::ptr::NonNull;
64use core::slice;
65
66/// A 24-byte owned byte string with inline small-string optimization.
67///
68/// Strings of up to 23 bytes live entirely inside the value (no allocation,
69/// no pointer chase); larger strings spill to a heap buffer. The
70/// discriminator is a single byte at offset 23 (the tag, which doubles as
71/// the inline length 0..=22 OR equals 0xFF when the heap variant is active).
72///
73/// See the crate root for layout details.
74#[repr(C)]
75/// # Examples
76///
77/// Short values live inline; longer ones move to the heap. The API does not
78/// change, but `heap_bytes` reports which happened, which is what the
79/// keyspace's memory accounting reads.
80///
81/// ```
82/// use kevy_bytes::SmallBytes;
83/// let short = SmallBytes::from_slice(b"hello");
84/// assert_eq!(short.as_slice(), b"hello");
85/// assert_eq!(short.len(), 5);
86/// assert_eq!(short.heap_bytes(), 0, "a short value allocates nothing");
87///
88/// let long = SmallBytes::from_slice(&[b'x'; 100]);
89/// assert_eq!(long.len(), 100);
90/// assert!(long.heap_bytes() >= 100, "a long value is on the heap");
91/// ```
92///
93/// ```
94/// use kevy_bytes::SmallBytes;
95/// assert!(SmallBytes::from_slice(b"").is_empty());
96/// ```
97pub union SmallBytes {
98    // pub(crate) so `eq.rs` can branch on the variant directly; the union
99    // itself stays private to this crate's own modules.
100    pub(crate) inline: Inline,
101    pub(crate) heap: Heap,
102}
103
104const _: () = {
105    assert!(mem::size_of::<SmallBytes>() == 24);
106    assert!(mem::align_of::<SmallBytes>() == mem::align_of::<usize>());
107};
108
109// SAFETY: the heap variant owns its allocation outright — `heap.ptr` is never
110// shared with another `SmallBytes` (clone allocates and copies) and nothing
111// behind it is interior-mutable, so moving the value to another thread hands
112// over sole ownership. The inline variant is plain bytes.
113unsafe impl Send for SmallBytes {}
114// SAFETY: every shared-reference method reads only; there is no interior
115// mutability anywhere in either variant, so concurrent readers observe the same
116// immutable bytes.
117unsafe impl Sync for SmallBytes {}
118
119impl SmallBytes {
120    /// Empty inline `SmallBytes` (zero allocation).
121    ///
122    /// # Examples
123    ///
124    /// `const`, so it can seed a static or an array without a run-time
125    /// initialiser:
126    ///
127    /// ```
128    /// use kevy_bytes::SmallBytes;
129    /// static EMPTY: SmallBytes = SmallBytes::new();
130    /// assert!(EMPTY.is_empty());
131    /// assert_eq!(EMPTY.heap_bytes(), 0);
132    /// ```
133    pub const fn new() -> Self {
134        Self { inline: Inline { data: [0; INLINE_CAP], tag: 0 } }
135    }
136
137    /// Construct from a byte slice — inline if `bytes.len() <= 23`, else heap.
138    ///
139    /// # Examples
140    ///
141    /// Twenty-three is the boundary, and it is exact:
142    ///
143    /// ```
144    /// use kevy_bytes::SmallBytes;
145    /// assert_eq!(SmallBytes::from_slice(&[b'x'; 23]).heap_bytes(), 0);
146    /// assert_eq!(SmallBytes::from_slice(&[b'x'; 24]).heap_bytes(), 24);
147    /// ```
148    pub fn from_slice(bytes: &[u8]) -> Self {
149        if bytes.len() <= INLINE_LEN_MAX as usize {
150            let mut data = [0u8; INLINE_CAP];
151            // SAFETY: bytes.len() ≤ 23 = data.len(); non-overlapping regions.
152            unsafe {
153                core::ptr::copy_nonoverlapping(bytes.as_ptr(), data.as_mut_ptr(), bytes.len());
154            }
155            Self { inline: Inline { data, tag: bytes.len() as u8 } }
156        } else {
157            Self::alloc_heap(bytes)
158        }
159    }
160
161    /// Take ownership of a `Vec<u8>` — inline if `vec.len() <= 23`, else **reuse
162    /// the vec's allocation** (no copy on the heap path).
163    ///
164    /// # Examples
165    ///
166    /// The heap path keeps the vec's own buffer, so a value that arrived as
167    /// a `Vec` is stored without a second copy:
168    ///
169    /// ```
170    /// use kevy_bytes::SmallBytes;
171    /// let v = vec![b'z'; 64];
172    /// let addr = v.as_ptr();
173    /// let b = SmallBytes::from_vec(v);
174    /// assert_eq!(b.as_slice().as_ptr(), addr, "same allocation, not a copy");
175    /// ```
176    ///
177    /// A short vec goes inline instead, and its allocation is released:
178    ///
179    /// ```
180    /// use kevy_bytes::SmallBytes;
181    /// assert_eq!(SmallBytes::from_vec(vec![b'a'; 4]).heap_bytes(), 0);
182    /// ```
183    pub fn from_vec(vec: Vec<u8>) -> Self {
184        if vec.len() <= INLINE_LEN_MAX as usize {
185            Self::from_slice(&vec)
186        } else {
187            let mut v = ManuallyDrop::new(vec);
188            // SAFETY: len > 22 ⇒ cap > 0 ⇒ Vec has an allocation, so the pointer
189            // is non-null. Vec guarantees a non-null pointer for any allocated
190            // Vec (and a dangling-but-non-null for empty, which we don't hit here).
191            let ptr = unsafe { NonNull::new_unchecked(v.as_mut_ptr()) };
192            let len = v.len();
193            let cap = v.capacity();
194            Self { heap: Heap::new(ptr, len, cap) }
195        }
196    }
197
198    #[inline]
199    fn alloc_heap(bytes: &[u8]) -> Self {
200        let len = bytes.len();
201        // `len > 22` (caller has already taken the heap branch) and `len` is
202        // a slice length ⇒ ≤ `isize::MAX` ⇒ well below the `usize::MAX -
203        // (align - 1)` bound `from_size_align_unchecked` needs. u8's align is 1.
204        // SAFETY: see above.
205        let layout = unsafe { Layout::from_size_align_unchecked(len, 1) };
206        // SAFETY: layout.size() > 0 (caller's heap branch guarantees len > 22).
207        let raw = unsafe { alloc(layout) };
208        let Some(ptr) = NonNull::new(raw) else { handle_alloc_error(layout) };
209        // SAFETY: alloc returned a writable region of `len` bytes; source is a
210        // disjoint slice.
211        unsafe {
212            core::ptr::copy_nonoverlapping(bytes.as_ptr(), ptr.as_ptr(), len);
213        }
214        Self { heap: Heap::new(ptr, len, len) }
215    }
216
217    /// True when stored inline; the byte at index 23 is the deciding tag in
218    /// either rep, so the check is a single load + compare.
219    #[inline]
220    fn is_inline(&self) -> bool {
221        // SAFETY: byte 23 is always initialised — either as Inline::tag (0..=22)
222        // or as the high byte of Heap::cap_and_tag (= 0xFF). Reading it through
223        // the Inline view is valid in either case (the union is `repr(C)`).
224        unsafe { self.inline.tag <= INLINE_LEN_MAX }
225    }
226
227    /// Number of bytes stored.
228    ///
229    /// # Examples
230    ///
231    /// The same answer either side of the inline boundary — which is the
232    /// point of the type: where the bytes live is not the caller's problem.
233    ///
234    /// ```
235    /// use kevy_bytes::SmallBytes;
236    /// assert_eq!(SmallBytes::from_slice(&[0u8; 22]).len(), 22);
237    /// assert_eq!(SmallBytes::from_slice(&[0u8; 23]).len(), 23);
238    /// ```
239    #[inline]
240    pub fn len(&self) -> usize {
241        if self.is_inline() {
242            // SAFETY: just verified `inline.tag` ≤ 23.
243            unsafe { self.inline.tag as usize }
244        } else {
245            // SAFETY: tag > 22 ⇒ heap variant is active.
246            unsafe { self.heap.length() }
247        }
248    }
249
250    /// Whether `len() == 0`.
251    ///
252    /// # Examples
253    ///
254    /// ```
255    /// use kevy_bytes::SmallBytes;
256    /// assert!(SmallBytes::from_slice(b"").is_empty());
257    /// assert!(!SmallBytes::from_slice(b"\0").is_empty(), "a NUL byte is a byte");
258    /// ```
259    #[inline]
260    pub fn is_empty(&self) -> bool {
261        self.len() == 0
262    }
263
264    /// Bytes this value holds on the heap (0 when inline). Lets memory-accounting
265    /// callers (e.g. `maxmemory` enforcement) charge only the off-stack footprint
266    /// without re-deriving the inline-length threshold.
267    ///
268    /// This is the **allocation**, not the live length, and those differ:
269    /// [`Self::from_vec`] adopts its argument's buffer as it stands, so a
270    /// `Vec` grown by `extend_from_slice` arrives with the doubling
271    /// ladder's slack still on it. Reporting `len` charged 360 bytes for
272    /// a 640-byte allocation on the eleventh `APPEND` to one key — and
273    /// `maxmemory` is what this number feeds, so the server could sit at
274    /// 1.8x its bound without evicting.
275    ///
276    /// # Examples
277    ///
278    /// This is what `maxmemory` charges, so an inline value must cost zero
279    /// — it is already inside the entry the keyspace has counted:
280    ///
281    /// ```
282    /// use kevy_bytes::SmallBytes;
283    /// assert_eq!(SmallBytes::from_slice(b"user:1").heap_bytes(), 0);
284    /// // `from_slice` allocates exactly, so here the two agree.
285    /// assert_eq!(SmallBytes::from_slice(&[b'x'; 1000]).heap_bytes(), 1000);
286    ///
287    /// // A buffer with slack does not, and the slack is real memory.
288    /// let mut v = Vec::with_capacity(4096);
289    /// v.extend_from_slice(&[b'x'; 1000]);
290    /// assert_eq!(SmallBytes::from_vec(v).heap_bytes(), 4096);
291    /// ```
292    #[inline]
293    pub fn heap_bytes(&self) -> usize {
294        if self.is_inline() {
295            0
296        } else {
297            // SAFETY: `is_inline()` was false, so byte 23 is 0xFF, which
298            // only the heap representation writes — the union holds a
299            // `Heap` and reading it through that view is the valid one.
300            unsafe { self.heap.capacity() }
301        }
302    }
303
304    /// Heap bytes a `SmallBytes` built from `bytes` would own.
305    ///
306    /// The same rule as [`Self::heap_bytes`], answerable without building
307    /// the value — for accounting a key by its slice before it is stored.
308    /// Exists so the inline threshold is not copied out of this crate:
309    /// it was, as the literal `22`, and any change to the boundary would
310    /// have silently mis-charged every key with nothing failing.
311    ///
312    /// # Examples
313    ///
314    /// ```
315    /// use kevy_bytes::SmallBytes;
316    /// assert_eq!(SmallBytes::heap_bytes_for(b"user:1"), 0);
317    /// assert_eq!(SmallBytes::heap_bytes_for(&[b'x'; 1000]), 1000);
318    /// ```
319    #[inline]
320    #[must_use]
321    pub fn heap_bytes_for(bytes: &[u8]) -> usize {
322        if bytes.len() <= INLINE_LEN_MAX as usize { 0 } else { bytes.len() }
323    }
324
325    /// Borrow the bytes (no allocation; same for inline and heap variants).
326    ///
327    /// # Examples
328    ///
329    /// ```
330    /// use kevy_bytes::SmallBytes;
331    /// let b = SmallBytes::from_slice(b"GET");
332    /// assert_eq!(b.as_slice(), b"GET");
333    /// assert_eq!(SmallBytes::new().as_slice(), b"");
334    /// ```
335    #[inline]
336    pub fn as_slice(&self) -> &[u8] {
337        if self.is_inline() {
338            // SAFETY: first `tag` bytes of `data` are valid (zero-init at construction).
339            unsafe { slice::from_raw_parts(self.inline.data.as_ptr(), self.inline.tag as usize) }
340        } else {
341            // SAFETY: heap variant active; ptr/len originate from a Vec or our own alloc.
342            unsafe { slice::from_raw_parts(self.heap.ptr.as_ptr(), self.heap.length()) }
343        }
344    }
345
346    /// Copy into a fresh `Vec<u8>` (clone semantics).
347    ///
348    /// # Examples
349    ///
350    /// ```
351    /// use kevy_bytes::SmallBytes;
352    /// let b = SmallBytes::from_slice(b"copy me");
353    /// assert_eq!(b.to_vec(), b"copy me");
354    /// assert_eq!(b.as_slice(), b"copy me", "the original still holds them");
355    /// ```
356    pub fn to_vec(&self) -> Vec<u8> {
357        self.as_slice().to_vec()
358    }
359
360    /// Consume self and return an owned `Vec<u8>`. The heap path reuses the
361    /// existing allocation; the inline path copies into a new vec.
362    ///
363    /// # Examples
364    ///
365    /// A heap value hands its buffer straight back, so a round trip through
366    /// `SmallBytes` costs no allocation at either end:
367    ///
368    /// ```
369    /// use kevy_bytes::SmallBytes;
370    /// let v = vec![b'q'; 128];
371    /// let addr = v.as_ptr();
372    /// assert_eq!(SmallBytes::from_vec(v).into_vec().as_ptr(), addr);
373    /// ```
374    ///
375    /// ```
376    /// use kevy_bytes::SmallBytes;
377    /// assert_eq!(SmallBytes::from_slice(b"short").into_vec(), b"short");
378    /// ```
379    pub fn into_vec(self) -> Vec<u8> {
380        if self.is_inline() {
381            self.as_slice().to_vec()
382            // self drops as inline — nothing to free.
383        } else {
384            // SAFETY: heap variant active.
385            let (ptr, len, cap) =
386                unsafe { (self.heap.ptr.as_ptr(), self.heap.length(), self.heap.capacity()) };
387            // Skip our Drop to avoid double-free; Vec::from_raw_parts now owns it.
388            let _do_not_drop = ManuallyDrop::new(self);
389            // SAFETY: ptr/len/cap originated from either a Vec<u8> (from_vec)
390            // or our own `alloc(Layout::array::<u8>(cap))` (alloc_heap, where
391            // cap == len) — both meet Vec::from_raw_parts' requirements.
392            unsafe { Vec::from_raw_parts(ptr, len, cap) }
393        }
394    }
395}
396
397impl Default for SmallBytes {
398    fn default() -> Self {
399        Self::new()
400    }
401}
402
403impl Drop for SmallBytes {
404    fn drop(&mut self) {
405        if self.is_inline() {
406            return;
407        }
408        // SAFETY: heap variant active; layout matches the one used at alloc
409        // time (either from Vec — Vec uses `Layout::array::<u8>(cap)` — or our
410        // own alloc_heap which used the same layout).
411        unsafe {
412            let cap = self.heap.capacity();
413            let layout = Layout::array::<u8>(cap).expect("the same layout succeeded at alloc time");
414            dealloc(self.heap.ptr.as_ptr(), layout);
415        }
416    }
417}
418
419impl Clone for SmallBytes {
420    /// Specialised clone that bypasses `as_slice → from_slice → alloc_heap`'s
421    /// two layered length checks. Inline variant is a bitwise union copy (no
422    /// branch through the slice path); heap variant goes straight to a single
423    /// `alloc + memcpy` keyed on the already-known heap length.
424    #[inline]
425    fn clone(&self) -> Self {
426        if self.is_inline() {
427            // SAFETY: `Inline` is `repr(C)` + `Copy`; bitwise copy is sound
428            // when the source is currently in the inline variant (the tag
429            // byte ≤ 23 is part of the bit pattern we're copying, so the
430            // discriminator stays correct).
431            unsafe { Self { inline: self.inline } }
432        } else {
433            // SAFETY: tag > 22 ⇒ heap variant is active.
434            unsafe { self.clone_heap() }
435        }
436    }
437}
438
439impl SmallBytes {
440    /// Heap-fast-path clone. Caller must have established that `self` is in
441    /// the heap variant.
442    ///
443    /// # Safety
444    /// `self.heap` must be the active union variant (i.e. `is_inline()` is
445    /// false). `self.heap.ptr` must point to `self.heap.len` valid bytes.
446    #[inline]
447    unsafe fn clone_heap(&self) -> Self {
448        // SAFETY: this fn is `unsafe` and its `# Safety` section makes the caller
449        // assert that the heap variant is the live one, which is what both reads here
450        // require.
451        let (src_ptr, len) = unsafe { (self.heap.ptr.as_ptr(), self.heap.length()) };
452        // SAFETY: `Layout::from_size_align_unchecked` requires a non-zero power-of-two
453        // alignment and a size that, rounded up to it, does not overflow `isize::MAX`.
454        // Alignment 1 satisfies the first. For the second: the heap variant is only
455        // taken when `len > 22`, and `CAP_MASK` keeps the capacity below 2^56, well
456        // under `isize::MAX` on every target kevy builds for.
457        let layout = unsafe { Layout::from_size_align_unchecked(len, 1) };
458        // SAFETY: `alloc` requires a layout of non-zero size; `len > 22` above gives it.
459        let raw = unsafe { alloc(layout) };
460        let Some(ptr) = NonNull::new(raw) else { handle_alloc_error(layout) };
461        // SAFETY: src has `len` valid bytes; dst is freshly-allocated for `len`
462        // bytes; regions are disjoint.
463        unsafe { core::ptr::copy_nonoverlapping(src_ptr, ptr.as_ptr(), len) };
464        Self { heap: Heap::new(ptr, len, len) }
465    }
466}
467
468#[cfg(test)]
469mod tests;