Skip to main content

mnemosyne_arena/scratch/aligned_buf/
mod.rs

1//! Stack-resident, fixed-capacity scratch buffer.
2//!
3//! [`AlignedBuf`] is the zero-heap complement to
4//! [`super::aligned_vec::AlignedVec`]: it stores up to `N` elements inline in
5//! the struct itself, making it a zero-cost abstraction for small hot-path
6//! buffers whose maximum size is known at compile time.
7//!
8//! # Design
9//!
10//! The backing store is `[MaybeUninit<T>; N]`. Only the first `len` slots are
11//! initialized; unwritten slots hold unspecified bytes. [`ScratchElement`]'s
12//! `Copy` super-bound means there are no destructors to run, so `clear` is a
13//! single field write and `pop` / `drop` never leak resources.
14//!
15//! # Zero heap
16//!
17//! No allocator call is ever made by `AlignedBuf`'s own methods. When
18//! monomorphized the optimizer can keep small instances entirely in registers
19//! or a stack frame.
20//!
21//! # Copy
22//!
23//! `AlignedBuf<T, N>` is `Copy` (every `ScratchElement` is). The copy
24//! includes the `len` field, so the semantics are identical to copying
25//! `[T; len]`.
26
27mod ops;
28mod traits;
29
30use crate::scratch::element::ScratchElement;
31use core::mem::MaybeUninit;
32
33/// A stack-resident, fixed-capacity buffer holding up to `N` elements of `T`.
34///
35/// All storage is inline — no heap allocation is ever performed. Use this
36/// instead of [`super::aligned_vec::AlignedVec`] when the upper bound on
37/// element count is known at compile time and fits on the stack.
38///
39/// # Example
40///
41/// ```rust
42/// use mnemosyne_arena::AlignedBuf;
43///
44/// let mut buf = AlignedBuf::<u32, 8>::new();
45/// buf.push(1);
46/// buf.push(2);
47/// assert_eq!(buf.as_slice(), &[1, 2]);
48/// assert_eq!(buf.pop(), Some(2));
49/// ```
50// `ScratchElement` already requires `Copy`, so the derived bounds add no
51// constraint an instantiation could fail. Deriving keeps the copy bitwise
52// rather than looping over the initialized prefix.
53#[derive(Clone, Copy)]
54pub struct AlignedBuf<T: ScratchElement, const N: usize> {
55    /// Inline storage for up to `N` elements.
56    data: [MaybeUninit<T>; N],
57    /// Number of initialized elements.
58    len: usize,
59}
60
61#[cfg(test)]
62mod tests {
63    use super::*;
64
65    #[test]
66    fn push_and_pop() {
67        let mut buf = AlignedBuf::<u32, 4>::new();
68        assert!(buf.is_empty());
69        assert_eq!(buf.capacity(), 4);
70        buf.push(10);
71        buf.push(20);
72        assert_eq!(buf.len(), 2);
73        assert!(!buf.is_full());
74        assert_eq!(buf.pop(), Some(20));
75        assert_eq!(buf.pop(), Some(10));
76        assert_eq!(buf.pop(), None);
77    }
78
79    #[test]
80    fn try_push_full() {
81        let mut buf = AlignedBuf::<u8, 2>::new();
82        assert!(buf.try_push(1));
83        assert!(buf.try_push(2));
84        assert!(buf.is_full());
85        assert!(!buf.try_push(3));
86        assert_eq!(buf.len(), 2);
87    }
88
89    #[test]
90    fn from_array_round_trip() {
91        let arr = [1u32, 2, 3, 4];
92        let buf = AlignedBuf::<u32, 4>::from_array(arr);
93        assert_eq!(buf.as_slice(), &[1, 2, 3, 4]);
94        let arr2: [u32; 4] = buf.into();
95        assert_eq!(arr2, [1, 2, 3, 4]);
96    }
97
98    #[test]
99    fn filled_and_clear() {
100        let mut buf = AlignedBuf::<f32, 8>::filled(core::f32::consts::PI);
101        assert_eq!(buf.len(), 8);
102        buf.clear();
103        assert!(buf.is_empty());
104    }
105
106    #[test]
107    fn truncate() {
108        let mut buf = AlignedBuf::<u64, 6>::filled(0);
109        buf.truncate(3);
110        assert_eq!(buf.len(), 3);
111        buf.truncate(10); // no-op
112        assert_eq!(buf.len(), 3);
113    }
114
115    #[test]
116    fn zero_fill() {
117        let mut buf = AlignedBuf::<u32, 4>::filled(0xDEAD_BEEF);
118        buf.zero_fill();
119        assert!(buf.as_slice().iter().all(|&x| x == 0));
120    }
121
122    #[test]
123    fn deref_slice_ops() {
124        let mut buf = AlignedBuf::<i32, 5>::new();
125        for i in 0..5i32 {
126            buf.push(i);
127        }
128        // via Deref
129        assert_eq!(buf.iter().copied().sum::<i32>(), 10);
130        assert_eq!(buf[2], 2);
131    }
132
133    #[test]
134    fn from_slice_truncating() {
135        let src = [1u32, 2, 3, 4, 5, 6];
136        let buf = AlignedBuf::<u32, 4>::from_slice_truncating(&src);
137        assert_eq!(buf.as_slice(), &[1, 2, 3, 4]);
138    }
139
140    #[test]
141    fn clone_and_copy() {
142        let mut buf = AlignedBuf::<u8, 4>::new();
143        buf.push(1);
144        buf.push(2);
145        let copy = buf;
146        let clone = buf;
147        assert_eq!(copy.as_slice(), &[1, 2]);
148        assert_eq!(clone.as_slice(), &[1, 2]);
149    }
150
151    #[test]
152    fn partial_eq() {
153        let mut a = AlignedBuf::<u32, 4>::new();
154        let mut b = AlignedBuf::<u32, 4>::new();
155        a.push(1);
156        a.push(2);
157        b.push(1);
158        b.push(2);
159        assert_eq!(a, b);
160        b.push(3);
161        assert_ne!(a, b);
162    }
163
164    #[test]
165    fn from_iter_truncates() {
166        let buf: AlignedBuf<u32, 3> = (0u32..10).collect();
167        assert_eq!(buf.as_slice(), &[0, 1, 2]);
168    }
169
170    #[test]
171    fn const_new_in_static() {
172        static BUF: AlignedBuf<u32, 8> = AlignedBuf::new();
173        assert!(BUF.is_empty());
174        assert_eq!(BUF.capacity(), 8);
175    }
176
177    #[test]
178    fn remaining() {
179        let mut buf = AlignedBuf::<u8, 4>::new();
180        assert_eq!(buf.remaining(), 4);
181        buf.push(0);
182        assert_eq!(buf.remaining(), 3);
183    }
184}