Skip to main content

concinnity_core/memory/
inline_vec.rs

1// A sequence that keeps its first element inline.
2//
3// Some populations hold exactly one item almost everywhere: an entity's draw
4// slots (one per mesh, and most props are one mesh), a parent's children. A
5// `Vec` charges each of those a heap block -- twenty thousand entities, twenty
6// thousand blocks of four bytes -- and a pointer chase to reach it on every
7// frame that walks them. Holding the first element inline removes both for the
8// common case while keeping the heap for the rest, so a caller does not have to
9// know which case it is in.
10//
11// Capacity one rather than a general N: one is expressible as an enum, which is
12// why this file contains no `unsafe`. A general inline capacity needs
13// `MaybeUninit` and a hand-written drop, and nothing has asked for it.
14//
15// Equality and ordering read the contents, never the representation: a
16// one-element inline value equals a one-element spilled one.
17
18use alloc::vec::Vec;
19use core::fmt;
20use core::ops::{Deref, DerefMut};
21
22/// A sequence holding its first element inline and spilling to the heap beyond
23/// that.
24///
25/// Derefs to `[T]`, so slice methods (`iter`, `len`, `contains`, indexing) are
26/// available directly.
27///
28/// Spilled storage is kept once acquired: removing elements does not move the
29/// remainder back inline, so a value that has grown never silently reallocates
30/// when it shrinks.
31pub struct InlineVec<T>(Repr<T>);
32
33#[derive(Clone)]
34enum Repr<T> {
35    Empty,
36    One(T),
37    Spilled(Vec<T>),
38}
39
40impl<T> InlineVec<T> {
41    /// An empty sequence, holding no allocation.
42    pub const fn new() -> Self {
43        Self(Repr::Empty)
44    }
45
46    /// A sequence holding exactly `value`, inline.
47    pub const fn one(value: T) -> Self {
48        Self(Repr::One(value))
49    }
50
51    // Whether the contents live on the heap rather than inline.
52    #[cfg(test)]
53    pub(crate) fn spilled(&self) -> bool {
54        matches!(self.0, Repr::Spilled(_))
55    }
56
57    /// The contents as a slice.
58    pub fn as_slice(&self) -> &[T] {
59        match &self.0 {
60            Repr::Empty => &[],
61            Repr::One(value) => core::slice::from_ref(value),
62            Repr::Spilled(values) => values,
63        }
64    }
65
66    /// The contents as a mutable slice.
67    pub fn as_mut_slice(&mut self) -> &mut [T] {
68        match &mut self.0 {
69            Repr::Empty => &mut [],
70            Repr::One(value) => core::slice::from_mut(value),
71            Repr::Spilled(values) => values,
72        }
73    }
74
75    /// Append `value`, spilling to the heap if this is the second element.
76    pub fn push(&mut self, value: T) {
77        match &mut self.0 {
78            Repr::Empty => self.0 = Repr::One(value),
79            Repr::Spilled(values) => values.push(value),
80            Repr::One(_) => {
81                let Repr::One(first) = core::mem::replace(&mut self.0, Repr::Empty) else {
82                    unreachable!("the arm above matched One")
83                };
84                self.0 = Repr::Spilled(alloc::vec![first, value]);
85            }
86        }
87    }
88
89    /// Remove and return the last element, or `None` when empty.
90    pub fn pop(&mut self) -> Option<T> {
91        match &mut self.0 {
92            Repr::Empty => None,
93            Repr::Spilled(values) => values.pop(),
94            Repr::One(_) => match core::mem::replace(&mut self.0, Repr::Empty) {
95                Repr::One(value) => Some(value),
96                _ => unreachable!("the arm above matched One"),
97            },
98        }
99    }
100
101    /// Drop every element for which `keep` returns false.
102    pub fn retain(&mut self, mut keep: impl FnMut(&T) -> bool) {
103        match &mut self.0 {
104            Repr::Empty => {}
105            Repr::Spilled(values) => values.retain(|value| keep(value)),
106            Repr::One(value) => {
107                if !keep(value) {
108                    self.0 = Repr::Empty;
109                }
110            }
111        }
112    }
113
114    /// Drop every element and release any heap storage.
115    pub fn clear(&mut self) {
116        self.0 = Repr::Empty;
117    }
118
119    // The contents as an owned `Vec`, allocating when they were held inline.
120    pub(crate) fn into_vec(self) -> Vec<T> {
121        match self.0 {
122            Repr::Empty => Vec::new(),
123            Repr::One(value) => alloc::vec![value],
124            Repr::Spilled(values) => values,
125        }
126    }
127}
128
129impl<T> Default for InlineVec<T> {
130    fn default() -> Self {
131        Self::new()
132    }
133}
134
135impl<T: Clone> Clone for InlineVec<T> {
136    fn clone(&self) -> Self {
137        Self(self.0.clone())
138    }
139}
140
141impl<T> Deref for InlineVec<T> {
142    type Target = [T];
143
144    fn deref(&self) -> &[T] {
145        self.as_slice()
146    }
147}
148
149impl<T> DerefMut for InlineVec<T> {
150    fn deref_mut(&mut self) -> &mut [T] {
151        self.as_mut_slice()
152    }
153}
154
155// Contents, not representation: `One(x)` and `Spilled(vec![x])` hold the same
156// sequence and must not be distinguishable.
157impl<T: PartialEq> PartialEq for InlineVec<T> {
158    fn eq(&self, other: &Self) -> bool {
159        self.as_slice() == other.as_slice()
160    }
161}
162
163impl<T: Eq> Eq for InlineVec<T> {}
164
165impl<T: PartialEq<U>, U, const N: usize> PartialEq<[U; N]> for InlineVec<T> {
166    fn eq(&self, other: &[U; N]) -> bool {
167        self.as_slice() == other.as_slice()
168    }
169}
170
171impl<T: PartialEq<U>, U> PartialEq<[U]> for InlineVec<T> {
172    fn eq(&self, other: &[U]) -> bool {
173        self.as_slice() == other
174    }
175}
176
177impl<T: PartialEq<U>, U> PartialEq<Vec<U>> for InlineVec<T> {
178    fn eq(&self, other: &Vec<U>) -> bool {
179        self.as_slice() == other.as_slice()
180    }
181}
182
183impl<T: fmt::Debug> fmt::Debug for InlineVec<T> {
184    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
185        self.as_slice().fmt(f)
186    }
187}
188
189impl<T> From<Vec<T>> for InlineVec<T> {
190    // Normalizes: a `Vec` of at most one element gives up its allocation.
191    fn from(values: Vec<T>) -> Self {
192        let mut values = values;
193        match values.len() {
194            0 => Self::new(),
195            1 => Self(Repr::One(values.pop().expect("length is one"))),
196            _ => Self(Repr::Spilled(values)),
197        }
198    }
199}
200
201impl<T, const N: usize> From<[T; N]> for InlineVec<T> {
202    fn from(values: [T; N]) -> Self {
203        values.into_iter().collect()
204    }
205}
206
207impl<T> From<InlineVec<T>> for Vec<T> {
208    fn from(inline: InlineVec<T>) -> Self {
209        inline.into_vec()
210    }
211}
212
213impl<T> FromIterator<T> for InlineVec<T> {
214    fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
215        let mut iter = iter.into_iter();
216        let Some(first) = iter.next() else {
217            return Self::new();
218        };
219        let Some(second) = iter.next() else {
220            return Self(Repr::One(first));
221        };
222        let mut values = Vec::with_capacity(iter.size_hint().0 + 2);
223        values.push(first);
224        values.push(second);
225        values.extend(iter);
226        Self(Repr::Spilled(values))
227    }
228}
229
230impl<T> Extend<T> for InlineVec<T> {
231    fn extend<I: IntoIterator<Item = T>>(&mut self, iter: I) {
232        for value in iter {
233            self.push(value);
234        }
235    }
236}
237
238/// By-value iterator over an [`InlineVec`], produced by `into_iter`.
239pub enum IntoIter<T> {
240    #[doc(hidden)]
241    Inline(core::option::IntoIter<T>),
242    #[doc(hidden)]
243    Spilled(alloc::vec::IntoIter<T>),
244}
245
246impl<T> Iterator for IntoIter<T> {
247    type Item = T;
248
249    fn next(&mut self) -> Option<T> {
250        match self {
251            Self::Inline(iter) => iter.next(),
252            Self::Spilled(iter) => iter.next(),
253        }
254    }
255
256    fn size_hint(&self) -> (usize, Option<usize>) {
257        match self {
258            Self::Inline(iter) => iter.size_hint(),
259            Self::Spilled(iter) => iter.size_hint(),
260        }
261    }
262}
263
264impl<T> ExactSizeIterator for IntoIter<T> {}
265
266impl<T> IntoIterator for InlineVec<T> {
267    type Item = T;
268    type IntoIter = IntoIter<T>;
269
270    fn into_iter(self) -> IntoIter<T> {
271        match self.0 {
272            Repr::Empty => IntoIter::Inline(None.into_iter()),
273            Repr::One(value) => IntoIter::Inline(Some(value).into_iter()),
274            Repr::Spilled(values) => IntoIter::Spilled(values.into_iter()),
275        }
276    }
277}
278
279impl<'a, T> IntoIterator for &'a InlineVec<T> {
280    type Item = &'a T;
281    type IntoIter = core::slice::Iter<'a, T>;
282
283    fn into_iter(self) -> core::slice::Iter<'a, T> {
284        self.as_slice().iter()
285    }
286}
287
288impl<'a, T> IntoIterator for &'a mut InlineVec<T> {
289    type Item = &'a mut T;
290    type IntoIter = core::slice::IterMut<'a, T>;
291
292    fn into_iter(self) -> core::slice::IterMut<'a, T> {
293        self.as_mut_slice().iter_mut()
294    }
295}
296
297#[cfg(test)]
298mod tests {
299    use super::*;
300    use alloc::vec;
301
302    #[test]
303    fn an_empty_value_holds_nothing_inline() {
304        let empty = InlineVec::<u32>::new();
305        assert!(empty.is_empty());
306        assert_eq!(empty.len(), 0);
307        assert!(!empty.spilled());
308        assert_eq!(empty.as_slice(), &[] as &[u32]);
309    }
310
311    // The whole point: the common case must not reach the heap.
312    #[test]
313    fn a_single_element_stays_inline() {
314        let mut one = InlineVec::new();
315        one.push(7u32);
316        assert!(!one.spilled(), "one element must not allocate");
317        assert_eq!(one.as_slice(), [7]);
318        assert_eq!(InlineVec::one(7u32).as_slice(), [7]);
319    }
320
321    #[test]
322    fn a_second_element_spills_to_the_heap_in_order() {
323        let mut many = InlineVec::new();
324        many.push(1u32);
325        many.push(2);
326        many.push(3);
327        assert!(many.spilled());
328        assert_eq!(many.as_slice(), [1, 2, 3]);
329    }
330
331    #[test]
332    fn collecting_picks_the_representation_from_the_length() {
333        let none: InlineVec<u32> = core::iter::empty().collect();
334        assert!(none.is_empty() && !none.spilled());
335
336        let one: InlineVec<u32> = core::iter::once(5).collect();
337        assert!(!one.spilled());
338        assert_eq!(one.as_slice(), [5]);
339
340        let many: InlineVec<u32> = (0..4).collect();
341        assert!(many.spilled());
342        assert_eq!(many.as_slice(), [0, 1, 2, 3]);
343    }
344
345    // A `Vec` handed in gives up an allocation it no longer needs, so a caller
346    // building through `Vec` is not stuck with the block forever.
347    #[test]
348    fn a_short_vec_gives_up_its_allocation() {
349        assert!(!InlineVec::from(vec![9u32]).spilled());
350        assert!(InlineVec::from(vec![9u32, 10]).spilled());
351        assert!(!InlineVec::<u32>::from(vec![]).spilled());
352        assert_eq!(InlineVec::from(vec![9u32, 10]).as_slice(), [9, 10]);
353    }
354
355    // Equality is over contents. Both representations can hold one element, and
356    // a caller must never be able to tell which it got.
357    #[test]
358    fn the_two_representations_compare_equal_on_equal_contents() {
359        let inline = InlineVec::one(4u32);
360        let mut spilled = InlineVec::new();
361        spilled.push(4u32);
362        spilled.push(5);
363        spilled.pop();
364
365        assert!(spilled.spilled() && !inline.spilled());
366        assert_eq!(inline, spilled);
367        assert_eq!(
368            alloc::format!("{inline:?}"),
369            alloc::format!("{spilled:?}"),
370            "the representation must not show in Debug"
371        );
372    }
373
374    #[test]
375    fn retain_drops_the_inline_element_and_filters_the_spilled_one() {
376        let mut one = InlineVec::one(1u32);
377        one.retain(|&v| v != 1);
378        assert!(one.is_empty());
379
380        let mut many: InlineVec<u32> = (0..6).collect();
381        many.retain(|v| v % 2 == 0);
382        assert_eq!(many.as_slice(), [0, 2, 4]);
383    }
384
385    // Documented behaviour: shrinking keeps the block rather than reallocating.
386    #[test]
387    fn a_shrunk_value_keeps_its_heap_storage_until_cleared() {
388        let mut many: InlineVec<u32> = (0..3).collect();
389        many.retain(|&v| v == 0);
390        assert!(many.spilled());
391        assert_eq!(many.as_slice(), [0]);
392
393        many.clear();
394        assert!(!many.spilled());
395        assert!(many.is_empty());
396    }
397
398    #[test]
399    fn pop_returns_elements_from_the_end_of_both_representations() {
400        let mut one = InlineVec::one(1u32);
401        assert_eq!(one.pop(), Some(1));
402        assert_eq!(one.pop(), None);
403
404        let mut many: InlineVec<u32> = (0..3).collect();
405        assert_eq!(many.pop(), Some(2));
406        assert_eq!(many.pop(), Some(1));
407        assert_eq!(many.pop(), Some(0));
408        assert_eq!(many.pop(), None);
409    }
410
411    #[test]
412    fn iteration_covers_every_representation() {
413        let cases = [
414            InlineVec::<u32>::new(),
415            InlineVec::one(1),
416            (1..4).collect::<InlineVec<u32>>(),
417        ];
418        let expected: [&[u32]; 3] = [&[], &[1], &[1, 2, 3]];
419
420        for (case, want) in cases.into_iter().zip(expected) {
421            assert_eq!(case.iter().copied().collect::<Vec<_>>(), want);
422            assert_eq!((&case).into_iter().copied().collect::<Vec<_>>(), want);
423            assert_eq!(case.clone().into_vec(), want);
424            assert_eq!(case.into_iter().collect::<Vec<_>>(), want);
425        }
426    }
427
428    #[test]
429    fn mutable_access_reaches_both_representations() {
430        let mut one = InlineVec::one(1u32);
431        for value in &mut one {
432            *value += 1;
433        }
434        assert_eq!(one.as_slice(), [2]);
435
436        let mut many: InlineVec<u32> = (0..3).collect();
437        many.as_mut_slice()[1] = 9;
438        assert_eq!(many.as_slice(), [0, 9, 2]);
439    }
440
441    #[test]
442    fn extend_grows_through_the_same_spill_rule() {
443        let mut values = InlineVec::new();
444        values.extend([1u32]);
445        assert!(!values.spilled());
446        values.extend([2u32, 3]);
447        assert_eq!(values.as_slice(), [1, 2, 3]);
448    }
449
450    // The inline element rides in the `Vec` pointer's niche, so a column of
451    // these is no wider than a column of `Vec`s and the saved heap block is not
452    // paid for in inline bytes. A representation change that broke the niche
453    // would grow every component column that holds one, silently.
454    #[test]
455    fn the_inline_element_costs_no_more_than_the_vec_it_replaces() {
456        assert_eq!(
457            size_of::<InlineVec<u32>>(),
458            size_of::<Vec<u32>>(),
459            "the inline case must ride in the Vec's niche"
460        );
461        assert_eq!(size_of::<InlineVec<u64>>(), size_of::<Vec<u64>>());
462        assert_eq!(
463            size_of::<InlineVec<(u32, u32)>>(),
464            size_of::<Vec<(u32, u32)>>()
465        );
466    }
467
468    // Comparing against a literal is what call sites and their tests do, so it
469    // reads the same whether the value spilled or not.
470    #[test]
471    fn arrays_and_vecs_convert_and_compare_across_both_representations() {
472        let one: InlineVec<u32> = [1].into();
473        let many: InlineVec<u32> = [1, 2].into();
474        assert!(!one.spilled() && many.spilled());
475
476        assert_eq!(one, [1]);
477        assert_eq!(many, [1, 2]);
478        assert_eq!(many, vec![1u32, 2]);
479        assert_eq!(many, *[1u32, 2].as_slice());
480        assert_ne!(one, [2]);
481        assert_ne!(one, [1, 2]);
482    }
483
484    // Slice methods arrive through `Deref`, so call sites that held a `Vec`
485    // keep reading the same way.
486    #[test]
487    fn slice_methods_are_available_through_deref() {
488        let values: InlineVec<u32> = (10..13).collect();
489        assert!(values.contains(&11));
490        assert_eq!(values[2], 12);
491        assert_eq!(values.first(), Some(&10));
492        assert_eq!(values.to_vec(), vec![10, 11, 12]);
493    }
494
495    // Ownership is real on both paths: dropping must run element destructors
496    // exactly once whether or not the value spilled.
497    #[test]
498    fn dropping_runs_element_destructors_once() {
499        use alloc::rc::Rc;
500
501        let witness = Rc::new(());
502        let mut values = InlineVec::new();
503        values.push(Rc::clone(&witness));
504        assert_eq!(Rc::strong_count(&witness), 2);
505
506        values.push(Rc::clone(&witness));
507        assert_eq!(Rc::strong_count(&witness), 3, "the spill must not leak");
508
509        drop(values);
510        assert_eq!(Rc::strong_count(&witness), 1);
511    }
512}