Skip to main content

scirs2_core/collections/
tiny_vec.rs

1//! `TinyVec<T, N>` — a hybrid stack/heap vector optimised for small sizes.
2//!
3//! Up to `N` elements are stored inline in a fixed-size array on the stack.
4//! When the number of elements exceeds `N` the backing storage automatically
5//! spills onto the heap and the inline array is abandoned.
6//!
7//! # Design goals
8//!
9//! - **Zero heap allocation** for collections that stay within `N` elements.
10//! - **Transparent API** that mirrors `Vec<T>` for push / pop / index / iter.
11//! - **No `unsafe` needed for the API** — the `MaybeUninit` usage is fully
12//!   contained in this module and carefully justified.
13
14use std::fmt;
15use std::mem::MaybeUninit;
16use std::ops::{Deref, DerefMut, Index, IndexMut};
17
18// ============================================================================
19// TinyVec
20// ============================================================================
21
22/// The maximum number of elements stored inline when `N == 0` at compile time.
23/// This case is degenerate; we still support it by treating capacity as 0.
24
25enum Storage<T, const N: usize> {
26    Inline {
27        data: [MaybeUninit<T>; N],
28        len: usize,
29    },
30    Heap(Vec<T>),
31}
32
33/// A hybrid stack/heap vector.
34///
35/// Elements are stored inline (on the stack) until `N` elements are exceeded,
36/// at which point the entire buffer is moved to the heap.
37///
38/// # Example
39///
40/// ```rust
41/// use scirs2_core::collections::TinyVec;
42///
43/// let mut v: TinyVec<i32, 4> = TinyVec::new();
44/// v.push(1);
45/// v.push(2);
46/// v.push(3);
47/// v.push(4); // still inline
48/// v.push(5); // spills to heap
49///
50/// assert_eq!(v.len(), 5);
51/// assert_eq!(v[0], 1);
52/// assert_eq!(v[4], 5);
53/// ```
54pub struct TinyVec<T, const N: usize> {
55    storage: Storage<T, N>,
56}
57
58impl<T, const N: usize> TinyVec<T, N> {
59    /// Creates a new, empty `TinyVec`.
60    pub fn new() -> Self {
61        TinyVec {
62            // SAFETY: An array of MaybeUninit is always valid in its uninitialised state.
63            storage: Storage::Inline {
64                data: unsafe { MaybeUninit::uninit().assume_init() },
65                len: 0,
66            },
67        }
68    }
69
70    /// Creates a `TinyVec` from an existing `Vec<T>`, immediately using heap storage.
71    pub fn from_vec(v: Vec<T>) -> Self {
72        TinyVec {
73            storage: Storage::Heap(v),
74        }
75    }
76
77    /// Returns the number of elements stored.
78    pub fn len(&self) -> usize {
79        match &self.storage {
80            Storage::Inline { len, .. } => *len,
81            Storage::Heap(v) => v.len(),
82        }
83    }
84
85    /// Returns `true` if there are no elements.
86    pub fn is_empty(&self) -> bool {
87        self.len() == 0
88    }
89
90    /// Returns `true` if the backing storage is currently on the stack.
91    pub fn is_inline(&self) -> bool {
92        matches!(&self.storage, Storage::Inline { .. })
93    }
94
95    /// Appends `value` to the end of the vector.
96    ///
97    /// If the inline buffer is full the vector spills to the heap automatically.
98    pub fn push(&mut self, value: T) {
99        match &mut self.storage {
100            Storage::Inline { data, len } => {
101                if *len < N {
102                    // SAFETY: `*len < N` so `data[*len]` is within bounds.
103                    unsafe {
104                        data[*len].as_mut_ptr().write(value);
105                    }
106                    *len += 1;
107                } else {
108                    // Spill to heap.
109                    self.spill_to_heap(value);
110                }
111            }
112            Storage::Heap(v) => v.push(value),
113        }
114    }
115
116    /// Removes and returns the last element, or `None` if empty.
117    pub fn pop(&mut self) -> Option<T> {
118        match &mut self.storage {
119            Storage::Inline { data, len } => {
120                if *len == 0 {
121                    return None;
122                }
123                *len -= 1;
124                // SAFETY: The element at index `*len` was initialised by a prior push.
125                let value = unsafe { data[*len].as_ptr().read() };
126                Some(value)
127            }
128            Storage::Heap(v) => v.pop(),
129        }
130    }
131
132    /// Returns a reference to the element at `index`, or `None` if out of range.
133    pub fn get(&self, index: usize) -> Option<&T> {
134        if index >= self.len() {
135            return None;
136        }
137        match &self.storage {
138            // SAFETY: index < len, so the slot is initialised.
139            Storage::Inline { data, .. } => Some(unsafe { &*data[index].as_ptr() }),
140            Storage::Heap(v) => v.get(index),
141        }
142    }
143
144    /// Returns a mutable reference to the element at `index`, or `None` if out of range.
145    pub fn get_mut(&mut self, index: usize) -> Option<&mut T> {
146        if index >= self.len() {
147            return None;
148        }
149        match &mut self.storage {
150            // SAFETY: index < len, so the slot is initialised.
151            Storage::Inline { data, .. } => Some(unsafe { &mut *data[index].as_mut_ptr() }),
152            Storage::Heap(v) => v.get_mut(index),
153        }
154    }
155
156    /// Returns a slice view of all elements.
157    pub fn as_slice(&self) -> &[T] {
158        match &self.storage {
159            // SAFETY: The first `len` elements are initialised.
160            Storage::Inline { data, len } => unsafe {
161                std::slice::from_raw_parts(data.as_ptr() as *const T, *len)
162            },
163            Storage::Heap(v) => v.as_slice(),
164        }
165    }
166
167    /// Returns a mutable slice view of all elements.
168    pub fn as_mut_slice(&mut self) -> &mut [T] {
169        match &mut self.storage {
170            // SAFETY: The first `len` elements are initialised.
171            Storage::Inline { data, len } => unsafe {
172                std::slice::from_raw_parts_mut(data.as_mut_ptr() as *mut T, *len)
173            },
174            Storage::Heap(v) => v.as_mut_slice(),
175        }
176    }
177
178    /// Clears the vector, dropping all elements.
179    pub fn clear(&mut self) {
180        match &mut self.storage {
181            Storage::Inline { data, len } => {
182                // Drop all initialised elements.
183                for i in 0..*len {
184                    // SAFETY: elements 0..*len are initialised.
185                    unsafe { data[i].as_mut_ptr().drop_in_place() };
186                }
187                *len = 0;
188            }
189            Storage::Heap(v) => v.clear(),
190        }
191    }
192
193    /// Converts the `TinyVec` into an owned `Vec<T>`, which may involve a heap
194    /// allocation if the data is currently stored inline.
195    pub fn into_vec(mut self) -> Vec<T> {
196        // Use ManuallyDrop to prevent the Drop impl from running after we
197        // move the storage out via ptr::read.
198        let storage = unsafe { std::ptr::read(&self.storage) };
199        // Prevent our Drop from double-freeing.
200        std::mem::forget(self);
201
202        match storage {
203            Storage::Inline { data, len } => {
204                let mut v = Vec::with_capacity(len);
205                for i in 0..len {
206                    // SAFETY: elements 0..len are initialised; we consume them.
207                    v.push(unsafe { data[i].as_ptr().read() });
208                }
209                // MaybeUninit<T> does not implement Drop, so no explicit
210                // forget is needed — the array is consumed by value above.
211                v
212            }
213            Storage::Heap(v) => v,
214        }
215    }
216
217    // ------------------------------------------------------------------
218    // Private helpers
219    // ------------------------------------------------------------------
220
221    /// Moves all inline elements to a new heap `Vec` and then pushes `extra`.
222    fn spill_to_heap(&mut self, extra: T) {
223        // We temporarily move `self.storage` out; this is safe because we
224        // immediately replace it.
225        let old_storage = std::mem::replace(
226            &mut self.storage,
227            // Placeholder — will be replaced before we return.
228            Storage::Heap(Vec::new()),
229        );
230
231        if let Storage::Inline { data, len } = old_storage {
232            let mut v = Vec::with_capacity(len + 1);
233            for i in 0..len {
234                // SAFETY: elements 0..len were initialised.
235                v.push(unsafe { data[i].as_ptr().read() });
236            }
237            // MaybeUninit<T> does not implement Drop — no forget needed.
238            v.push(extra);
239            self.storage = Storage::Heap(v);
240        }
241        // The placeholder `Storage::Heap(Vec::new())` case should never be reached.
242    }
243}
244
245// ============================================================================
246// Drop
247// ============================================================================
248
249impl<T, const N: usize> Drop for TinyVec<T, N> {
250    fn drop(&mut self) {
251        // The Heap variant drops T elements automatically via Vec's Drop.
252        // For the Inline variant we must manually drop initialised elements.
253        if let Storage::Inline { data, len } = &mut self.storage {
254            for i in 0..*len {
255                // SAFETY: elements 0..*len are initialised.
256                unsafe { data[i].as_mut_ptr().drop_in_place() };
257            }
258            // Set len = 0 so a hypothetical future double-drop is a no-op.
259            *len = 0;
260        }
261    }
262}
263
264// ============================================================================
265// Trait implementations
266// ============================================================================
267
268impl<T, const N: usize> Deref for TinyVec<T, N> {
269    type Target = [T];
270    fn deref(&self) -> &[T] {
271        self.as_slice()
272    }
273}
274
275impl<T, const N: usize> DerefMut for TinyVec<T, N> {
276    fn deref_mut(&mut self) -> &mut [T] {
277        self.as_mut_slice()
278    }
279}
280
281impl<T, const N: usize> Index<usize> for TinyVec<T, N> {
282    type Output = T;
283    fn index(&self, index: usize) -> &T {
284        self.get(index).expect("TinyVec: index out of bounds")
285    }
286}
287
288impl<T, const N: usize> IndexMut<usize> for TinyVec<T, N> {
289    fn index_mut(&mut self, index: usize) -> &mut T {
290        self.get_mut(index).expect("TinyVec: index out of bounds")
291    }
292}
293
294impl<T: Clone, const N: usize> Clone for TinyVec<T, N> {
295    fn clone(&self) -> Self {
296        let mut out = TinyVec::new();
297        for elem in self.as_slice() {
298            out.push(elem.clone());
299        }
300        out
301    }
302}
303
304impl<T: fmt::Debug, const N: usize> fmt::Debug for TinyVec<T, N> {
305    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
306        fmt::Debug::fmt(self.as_slice(), f)
307    }
308}
309
310impl<T: PartialEq, const N: usize> PartialEq for TinyVec<T, N> {
311    fn eq(&self, other: &Self) -> bool {
312        self.as_slice() == other.as_slice()
313    }
314}
315
316impl<T: Eq, const N: usize> Eq for TinyVec<T, N> {}
317
318impl<T, const N: usize> FromIterator<T> for TinyVec<T, N> {
319    fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
320        let mut v = TinyVec::new();
321        for item in iter {
322            v.push(item);
323        }
324        v
325    }
326}
327
328impl<T, const N: usize> IntoIterator for TinyVec<T, N> {
329    type Item = T;
330    type IntoIter = std::vec::IntoIter<T>;
331    fn into_iter(self) -> Self::IntoIter {
332        self.into_vec().into_iter()
333    }
334}
335
336impl<'a, T, const N: usize> IntoIterator for &'a TinyVec<T, N> {
337    type Item = &'a T;
338    type IntoIter = std::slice::Iter<'a, T>;
339    fn into_iter(self) -> Self::IntoIter {
340        self.as_slice().iter()
341    }
342}
343
344// ============================================================================
345// Tests
346// ============================================================================
347
348#[cfg(test)]
349mod tests {
350    use super::*;
351
352    #[test]
353    fn test_push_within_inline() {
354        let mut v: TinyVec<i32, 4> = TinyVec::new();
355        v.push(10);
356        v.push(20);
357        v.push(30);
358        assert!(v.is_inline());
359        assert_eq!(v.len(), 3);
360        assert_eq!(v[0], 10);
361        assert_eq!(v[2], 30);
362    }
363
364    #[test]
365    fn test_spill_to_heap() {
366        let mut v: TinyVec<i32, 2> = TinyVec::new();
367        v.push(1);
368        v.push(2);
369        assert!(v.is_inline());
370        v.push(3); // triggers spill
371        assert!(!v.is_inline());
372        assert_eq!(v.len(), 3);
373        assert_eq!(v[0], 1);
374        assert_eq!(v[1], 2);
375        assert_eq!(v[2], 3);
376    }
377
378    #[test]
379    fn test_pop() {
380        let mut v: TinyVec<i32, 4> = TinyVec::new();
381        v.push(42);
382        assert_eq!(v.pop(), Some(42));
383        assert_eq!(v.pop(), None);
384    }
385
386    #[test]
387    fn test_pop_after_spill() {
388        let mut v: TinyVec<i32, 1> = TinyVec::new();
389        v.push(1);
390        v.push(2);
391        assert_eq!(v.pop(), Some(2));
392        assert_eq!(v.pop(), Some(1));
393        assert_eq!(v.pop(), None);
394    }
395
396    #[test]
397    fn test_clear() {
398        let mut v: TinyVec<String, 4> = TinyVec::new();
399        v.push("hello".to_string());
400        v.push("world".to_string());
401        v.clear();
402        assert!(v.is_empty());
403    }
404
405    #[test]
406    fn test_drop_non_copy() {
407        // Ensure that strings are properly dropped (checked via Miri in CI).
408        let mut v: TinyVec<String, 2> = TinyVec::new();
409        v.push("a".to_string());
410        v.push("b".to_string());
411        v.push("c".to_string()); // spill
412        drop(v);
413    }
414
415    #[test]
416    fn test_iter() {
417        let mut v: TinyVec<i32, 4> = TinyVec::new();
418        for i in 0..6 {
419            v.push(i);
420        }
421        let collected: Vec<_> = v.iter().copied().collect();
422        assert_eq!(collected, vec![0, 1, 2, 3, 4, 5]);
423    }
424
425    #[test]
426    fn test_from_iter() {
427        let v: TinyVec<i32, 4> = (0..8).collect();
428        assert_eq!(v.len(), 8);
429        for (i, &x) in v.iter().enumerate() {
430            assert_eq!(x, i as i32);
431        }
432    }
433
434    #[test]
435    fn test_clone() {
436        let mut v: TinyVec<i32, 4> = TinyVec::new();
437        v.push(1);
438        v.push(2);
439        let w = v.clone();
440        assert_eq!(v, w);
441    }
442
443    #[test]
444    fn test_zero_capacity() {
445        // N = 0: every push immediately triggers a heap allocation.
446        let mut v: TinyVec<i32, 0> = TinyVec::new();
447        v.push(99);
448        assert_eq!(v.len(), 1);
449        assert_eq!(v[0], 99);
450    }
451}