zigzag_alloc/collections/bounded_array.rs
1//! Fixed-capacity, stack-allocated array.
2//!
3//! [`ExBoundedArray<T, N>`] stores up to `N` elements of type `T` directly on
4//! the stack without any heap allocation. The capacity is a compile-time
5//! constant; attempting to exceed it returns an error or panics, depending on
6//! the method called.
7//!
8//! ## When to Use
9//!
10//! * Small, short-lived buffers whose maximum size is known at compile time.
11//! * `no_std` environments without a heap.
12//! * Hot paths where heap-allocation overhead is unacceptable.
13//!
14//! ## SIMD Extensions (`ExBoundedArray<u8, N>`)
15//!
16//! When `T = u8`, additional byte-level SIMD methods are available:
17//! [`fill_bytes`](ExBoundedArray::fill_bytes),
18//! [`fill_range`](ExBoundedArray::fill_range),
19//! [`find_byte`](ExBoundedArray::find_byte),
20//! [`count_byte`](ExBoundedArray::count_byte),
21//! [`extend_bytes`](ExBoundedArray::extend_bytes).
22
23use core::{
24 mem::MaybeUninit,
25 ops::{Deref, DerefMut, Index, IndexMut},
26 ptr,
27 slice
28};
29
30use crate::simd;
31
32/// A fixed-capacity, stack-allocated array.
33///
34/// # Invariants
35///
36/// * Elements at indices `0..len` are fully initialised.
37/// * Elements at indices `len..N` are uninitialised (`MaybeUninit`) and must
38/// not be read through safe interfaces.
39/// * `len <= N` at all times.
40pub struct ExBoundedArray<T, const N: usize> {
41 /// Storage for up to `N` elements; only `data[0..len]` are initialised.
42 data: [MaybeUninit<T>; N],
43 /// Number of initialised elements.
44 len: usize,
45}
46
47impl<T, const N: usize> ExBoundedArray<T, N> {
48 /// Creates an empty `ExBoundedArray` with no initialised elements.
49 ///
50 /// This is a `const fn` and can be used in `static` initialisers.
51 #[inline]
52 pub const fn new() -> Self {
53 Self {
54 // SAFETY: An array of `MaybeUninit<T>` does not require
55 // initialisation — interpreting uninitialised bytes as
56 // `MaybeUninit<T>` is always valid.
57 data: unsafe {
58 MaybeUninit::<[MaybeUninit<T>; N]>::uninit().assume_init()
59 },
60 len: 0,
61 }
62 }
63
64 /// Returns the number of initialised elements.
65 #[inline] pub fn len(&self) -> usize { self.len }
66 /// Returns the compile-time maximum capacity `N`.
67 #[inline] pub fn capacity(&self) -> usize { N }
68 /// Returns the number of additional elements that can be pushed.
69 #[inline] pub fn remaining(&self) -> usize { N - self.len }
70 /// Returns `true` if no elements are stored.
71 #[inline] pub fn is_empty(&self) -> bool { self.len == 0 }
72 /// Returns `true` if the array has reached its maximum capacity.
73 #[inline] pub fn is_full(&self) -> bool { self.len == N }
74
75 /// Returns a shared slice of the initialised elements.
76 #[inline]
77 pub fn as_slice(&self) -> &[T] {
78 // SAFETY: `data[0..len]` are all initialised; casting `*const MaybeUninit<T>`
79 // to `*const T` is valid when the slots are initialised.
80 unsafe { slice::from_raw_parts(self.data.as_ptr() as *const T, self.len) }
81 }
82
83 /// Returns a mutable slice of the initialised elements.
84 #[inline]
85 pub fn as_mut_slice(&mut self) -> &mut [T] {
86 // SAFETY: Same as `as_slice`, plus unique access via `&mut self`.
87 unsafe { slice::from_raw_parts_mut(self.data.as_mut_ptr() as *mut T, self.len) }
88 }
89
90 /// Appends `value` to the end of the array.
91 ///
92 /// Returns `Err(value)` if the array is full.
93 #[inline]
94 pub fn push(&mut self, value: T) -> Result<(), T> {
95 if self.len == N { return Err(value); }
96 self.data[self.len] = MaybeUninit::new(value);
97 self.len += 1;
98 Ok(())
99 }
100
101 /// Appends `value` without checking the capacity.
102 ///
103 /// # Safety
104 ///
105 /// `self.len < N` must hold; calling this when the array is full causes a
106 /// write past the end of `data`, which is undefined behaviour.
107 #[inline]
108 pub unsafe fn push_unchecked(&mut self, value: T) {
109 debug_assert!(self.len < N);
110 self.data[self.len] = MaybeUninit::new(value);
111 self.len += 1;
112 }
113
114 /// Removes and returns the last element, or `None` if empty.
115 #[inline]
116 pub fn pop(&mut self) -> Option<T> {
117 if self.len == 0 { return None; }
118 self.len -= 1;
119 // SAFETY: `data[len]` (old `len - 1`) was initialised; we read it
120 // before decrementing `len`, effectively transferring ownership.
121 Some(unsafe { self.data[self.len].assume_init_read() })
122 }
123
124 /// Inserts `value` at position `idx`, shifting all elements after it to
125 /// the right.
126 ///
127 /// Returns `Err(value)` if the array is already full.
128 ///
129 /// # Panics
130 ///
131 /// Panics if `idx > self.len`.
132 pub fn insert(&mut self, idx: usize, value: T) -> Result<(), T> {
133 if self.len == N { return Err(value); }
134 assert!(idx <= self.len, "ExBoundedArray::insert: index out of bounds");
135 unsafe {
136 // SAFETY: Shifting `len - idx` initialised elements one position
137 // to the right. Source and destination may overlap, so we use
138 // `copy` (not `copy_nonoverlapping`). The destination slot at
139 // `idx + 1..=len` is within bounds because `len < N`.
140 ptr::copy(
141 self.data.as_ptr().add(idx),
142 self.data.as_mut_ptr().add(idx + 1),
143 self.len - idx,
144 );
145 self.data[idx] = MaybeUninit::new(value);
146 }
147 self.len += 1;
148 Ok(())
149 }
150
151 /// Removes the element at `idx` and returns it, shifting subsequent
152 /// elements one position to the left.
153 ///
154 /// # Panics
155 ///
156 /// Panics if `idx >= self.len`.
157 pub fn remove(&mut self, idx: usize) -> T {
158 assert!(idx < self.len, "ExBoundedArray::remove: index out of bounds");
159 unsafe {
160 // SAFETY: `data[idx]` is initialised; we read it before overwriting
161 // its slot via the `copy` below.
162 let val = self.data[idx].assume_init_read();
163 // SAFETY: Shifting `len - idx - 1` elements one position to the
164 // left. Source and destination overlap; `copy` is correct.
165 ptr::copy(
166 self.data.as_ptr().add(idx + 1),
167 self.data.as_mut_ptr().add(idx),
168 self.len - idx - 1,
169 );
170 self.len -= 1;
171 val
172 }
173 }
174
175 /// Removes the element at `idx` and fills the gap by moving the *last*
176 /// element into it.
177 ///
178 /// This is O(1) but does **not** preserve the order of elements.
179 ///
180 /// # Panics
181 ///
182 /// Panics if `idx >= self.len`.
183 pub fn swap_remove(&mut self, idx: usize) -> T {
184 assert!(idx < self.len, "ExBoundedArray::swap_remove: index out of bounds");
185 self.len -= 1;
186 unsafe {
187 if idx != self.len {
188 // SAFETY: Both `data[len]` and `data[idx]` are initialised.
189 // We read both and write the last element back into `idx`.
190 let last = self.data[self.len].assume_init_read();
191 let val = self.data[idx].assume_init_read();
192 self.data[idx] = MaybeUninit::new(last);
193 val
194 } else {
195 // Removing the last element — just read it.
196 // SAFETY: `data[len]` was initialised and `len` was decremented
197 // above, so the slot is no longer considered live.
198 self.data[self.len].assume_init_read()
199 }
200 }
201 }
202
203 /// Shortens the array to `new_len`, dropping excess elements.
204 ///
205 /// Does nothing if `new_len >= self.len`.
206 pub fn truncate(&mut self, new_len: usize) {
207 if new_len >= self.len { return; }
208 let old_len = self.len;
209 self.len = new_len;
210 for i in new_len..old_len {
211 // SAFETY: Indices `new_len..old_len` are initialised.
212 unsafe { ptr::drop_in_place(self.data[i].as_mut_ptr()); }
213 }
214 }
215
216 /// Removes all elements (drops each one).
217 pub fn clear(&mut self) { self.truncate(0); }
218
219 /// Appends copies of all items in `items` to the array.
220 ///
221 /// Returns `Err(n)` where `n` is the number of elements that did not fit.
222 /// On error, as many items as possible have been pushed.
223 ///
224 /// Requires `T: Copy`; uses SIMD bulk-copy for efficiency.
225 pub fn push_slice(&mut self, items: &[T]) -> Result<(), usize> where T: Copy {
226 let n = items.len();
227 if n == 0 { return Ok(()); }
228 if self.len + n > N {
229 return Err(n - (N - self.len));
230 }
231 let dst = unsafe { self.data.as_mut_ptr().add(self.len) } as *mut u8;
232 let src = items.as_ptr() as *const u8;
233 // SAFETY: `dst` points to uninitialised slots for exactly `n` elements;
234 // `src` is a valid slice of `n` initialised elements of the same type.
235 // `T: Copy` means no destructor is skipped.
236 unsafe { simd::copy_bytes(dst, src, n * core::mem::size_of::<T>()) };
237 self.len += n;
238 Ok(())
239 }
240
241 /// Replaces all current elements with copies from `items`.
242 ///
243 /// Drops all existing elements first, then bulk-copies the new ones.
244 ///
245 /// Returns `Err(())` if `items.len() > N`.
246 pub fn copy_from_slice(&mut self, items: &[T]) -> Result<(), ()> where T: Copy {
247 if items.len() > N { return Err(()); }
248 // Drop existing elements.
249 for i in 0..self.len {
250 // SAFETY: Indices `0..len` are initialised.
251 unsafe { ptr::drop_in_place(self.data[i].as_mut_ptr()) };
252 }
253 let n = items.len();
254 let dst = self.data.as_mut_ptr() as *mut u8;
255 let src = items.as_ptr() as *const u8;
256 // SAFETY: `dst` can hold at least `N >= n` elements; `src` is valid
257 // for `n` elements of `T`.
258 unsafe { simd::copy_bytes(dst, src, n * core::mem::size_of::<T>()) };
259 self.len = n;
260 Ok(())
261 }
262}
263
264impl<const N: usize> ExBoundedArray<u8, N> {
265 /// Fills all `len` initialised bytes with `val` using SIMD.
266 pub fn fill_bytes(&mut self, val: u8) {
267 if self.len == 0 { return; }
268 // SAFETY: `data.as_mut_ptr()` is valid for `len` initialised bytes.
269 unsafe { simd::fill_bytes(self.data.as_mut_ptr() as *mut u8, val, self.len) };
270 }
271
272 /// Fills the subrange `[start, start + len)` with `val` using SIMD.
273 ///
274 /// # Panics
275 ///
276 /// Panics if `start + len > self.len`.
277 pub fn fill_range(&mut self, start: usize, len: usize, val: u8) {
278 assert!(start + len <= self.len, "ExBoundedArray::fill_range: out of bounds");
279 // SAFETY: The bounds check above guarantees the target range is within
280 // the initialised region.
281 unsafe {
282 simd::fill_bytes(self.data.as_mut_ptr().add(start) as *mut u8, val, len)
283 };
284 }
285
286 /// Returns the offset of the first byte equal to `val`, or `None`.
287 pub fn find_byte(&self, val: u8) -> Option<usize> {
288 if self.len == 0 { return None; }
289 // SAFETY: `data.as_ptr()` is valid for `len` initialised bytes.
290 unsafe { simd::find_byte(self.data.as_ptr() as *const u8, val, self.len) }
291 }
292
293 /// Returns the number of bytes equal to `val` in the initialised region.
294 pub fn count_byte(&self, val: u8) -> usize {
295 let ptr = self.data.as_ptr() as *const u8;
296 let mut count = 0usize;
297 let mut i = 0usize;
298 while i < self.len {
299 match unsafe { simd::find_byte(ptr.add(i), val, self.len - i) } {
300 Some(off) => { count += 1; i += off + 1; }
301 None => break,
302 }
303 }
304 count
305 }
306
307 /// Appends all bytes from `src` to the array.
308 ///
309 /// Returns `true` on success, `false` if there is not enough remaining
310 /// capacity.
311 pub fn extend_bytes(&mut self, src: &[u8]) -> bool {
312 self.push_slice(src).is_ok()
313 }
314}
315
316impl<T, const N: usize> Default for ExBoundedArray<T, N> {
317 fn default() -> Self { Self::new() }
318}
319
320impl<T, const N: usize> Drop for ExBoundedArray<T, N> {
321 /// Drops all initialised elements. No heap memory is freed because the
322 /// array is stack-allocated.
323 fn drop(&mut self) {
324 for i in 0..self.len {
325 // SAFETY: Indices `0..len` are initialised.
326 unsafe { ptr::drop_in_place(self.data[i].as_mut_ptr()) };
327 }
328 }
329}
330
331impl<T, const N: usize> Deref for ExBoundedArray<T, N> {
332 type Target = [T];
333 fn deref(&self) -> &[T] { self.as_slice() }
334}
335
336impl<T, const N: usize> DerefMut for ExBoundedArray<T, N> {
337 fn deref_mut(&mut self) -> &mut [T] { self.as_mut_slice() }
338}
339
340impl<T, const N: usize> Index<usize> for ExBoundedArray<T, N> {
341 type Output = T;
342 fn index(&self, i: usize) -> &T { &self.as_slice()[i] }
343}
344
345impl<T, const N: usize> IndexMut<usize> for ExBoundedArray<T, N> {
346 fn index_mut(&mut self, i: usize) -> &mut T { &mut self.as_mut_slice()[i] }
347}