zigzag_alloc/collections/vec.rs
1//! Growable contiguous array with an explicit allocator.
2//!
3//! [`ExVec<T>`] is a dynamically-sized list that stores its elements in a
4//! heap-allocated buffer obtained from a user-supplied [`Allocator`]. It is
5//! functionally analogous to [`std::vec::Vec`] but never touches a global
6//! allocator.
7//!
8//! ## Growth Policy
9//!
10//! The initial capacity is **4**. Every time the buffer is exhausted, the
11//! capacity is **doubled** (`new_cap = old_cap * 2`). Data is migrated to the
12//! new buffer using SIMD-accelerated `copy_bytes`.
13//!
14//! ## SIMD Extensions (`ExVec<u8>`)
15//!
16//! When `T = u8`, additional methods are available:
17//! [`simd_fill`](ExVec::simd_fill), [`find_byte`](ExVec::find_byte),
18//! [`for_each_byte_match`](ExVec::for_each_byte_match),
19//! [`extend_filled`](ExVec::extend_filled).
20
21use core::{
22 alloc::Layout,
23 marker::PhantomData,
24 mem::MaybeUninit,
25 ops::{Deref, DerefMut, Index, IndexMut},
26 ptr::{self, NonNull},
27 slice,
28};
29
30use crate::alloc::allocator::Allocator;
31use crate::simd;
32
33/// A contiguous growable array backed by an explicit allocator.
34///
35/// # Invariants
36///
37/// * `ptr` is a valid, aligned allocation of `cap * size_of::<T>()` bytes
38/// whenever `cap > 0`. When `cap == 0`, `ptr` is a dangling non-null
39/// pointer (never dereferenced).
40/// * Elements at indices `0..len` are fully initialised.
41/// * Elements at indices `len..cap` are uninitialised and must not be read.
42///
43/// # Lifetime
44///
45/// The allocator reference `'a` must outlive the `ExVec`. The vec's buffer
46/// is freed in `Drop` using the same allocator reference.
47pub struct ExVec<'a, T> {
48 /// Pointer to the start of the allocated buffer.
49 ptr: NonNull<T>,
50 /// Number of initialised elements.
51 len: usize,
52 /// Total buffer capacity in elements.
53 cap: usize,
54 /// Allocator used for growth and deallocation.
55 alloc: &'a dyn Allocator,
56 _marker: PhantomData<T>,
57}
58
59impl<'a, T> ExVec<'a, T> {
60 /// Creates a new, empty `ExVec` that will use `alloc` for memory.
61 ///
62 /// No allocation is performed until the first element is pushed.
63 pub fn new(alloc: &'a dyn Allocator) -> Self {
64 Self { ptr: NonNull::dangling(), len: 0, cap: 0, alloc, _marker: PhantomData }
65 }
66
67 /// Returns the number of initialised elements.
68 #[inline] pub fn len(&self) -> usize { self.len }
69 /// Returns the total buffer capacity in elements.
70 #[inline] pub fn capacity(&self) -> usize { self.cap }
71 /// Returns `true` if the vec contains no elements.
72 #[inline] pub fn is_empty(&self) -> bool { self.len == 0 }
73 /// Returns a raw pointer to the start of the buffer.
74 ///
75 /// The pointer is only valid to dereference for indices `0..len`.
76 #[inline] pub fn as_ptr(&self) -> *const T { self.ptr.as_ptr() }
77
78 /// Returns a shared slice of the initialised elements.
79 #[inline]
80 pub fn as_slice(&self) -> &[T] {
81 // SAFETY: `ptr` is valid for `len` initialised elements of type `T`.
82 unsafe { slice::from_raw_parts(self.ptr.as_ptr(), self.len) }
83 }
84
85 /// Returns a mutable slice of the initialised elements.
86 #[inline]
87 pub fn as_mut_slice(&mut self) -> &mut [T] {
88 // SAFETY: Same as `as_slice`, plus unique access via `&mut self`.
89 unsafe { slice::from_raw_parts_mut(self.ptr.as_ptr(), self.len) }
90 }
91
92 /// Appends `value` to the end of the vec.
93 ///
94 /// # Panics
95 ///
96 /// Panics if the allocator cannot satisfy the growth request.
97 pub fn push(&mut self, value: T) {
98 if self.len == self.cap { self.grow(); }
99 // SAFETY: After `grow`, `cap > len`, so `ptr + len` is within the
100 // allocated buffer and currently uninitialised — safe to write.
101 unsafe { self.ptr.as_ptr().add(self.len).write(value) };
102 self.len += 1;
103 }
104
105 /// Attempts to append `value`, returning `Err(value)` if OOM.
106 pub fn try_push(&mut self, value: T) -> Result<(), T> {
107 if self.len == self.cap && !self.try_grow() { return Err(value); }
108 // SAFETY: Same as `push`.
109 unsafe { self.ptr.as_ptr().add(self.len).write(value) };
110 self.len += 1;
111 Ok(())
112 }
113
114 /// Removes and returns the last element, or `None` if the vec is empty.
115 pub fn pop(&mut self) -> Option<T> {
116 if self.len == 0 { return None; }
117 self.len -= 1;
118 // SAFETY: Element at `len` (old `len - 1`) was initialised; we
119 // decrement `len` first so the slot is no longer considered initialised.
120 Some(unsafe { self.ptr.as_ptr().add(self.len).read() })
121 }
122
123 /// Shortens the vec, keeping the first `new_len` elements and dropping
124 /// the rest. Does nothing if `new_len >= self.len`.
125 pub fn truncate(&mut self, new_len: usize) {
126 if new_len >= self.len { return; }
127 let old_len = self.len;
128 self.len = new_len;
129 for i in new_len..old_len {
130 // SAFETY: Indices in `new_len..old_len` are initialised; we drop
131 // them in-place as we reduce `len`.
132 unsafe { ptr::drop_in_place(self.ptr.as_ptr().add(i)) };
133 }
134 }
135
136 /// Removes all elements, dropping each one.
137 pub fn clear(&mut self) { self.truncate(0); }
138
139 /// Forcibly sets the length to `new_len`.
140 ///
141 /// # Safety
142 ///
143 /// * `new_len` must be ≤ `cap`.
144 /// * Elements in `old_len..new_len` (if extending) must have been
145 /// initialised by the caller before or immediately after this call.
146 /// * Elements in `new_len..old_len` (if shrinking) must have already been
147 /// dropped by the caller, or be types that do not need dropping.
148 pub unsafe fn set_len(&mut self, new_len: usize) {
149 debug_assert!(new_len <= self.cap);
150 self.len = new_len;
151 }
152
153 /// Appends a copy of every element in `items` to the vec.
154 ///
155 /// Grows the buffer (possibly multiple times) to fit all items.
156 ///
157 /// # Panics
158 ///
159 /// Panics if the allocator cannot satisfy any required growth.
160 pub fn push_slice(&mut self, items: &[T]) where T: Copy {
161 let n = items.len();
162 if n == 0 { return; }
163
164 while self.cap < self.len + n {
165 self.grow();
166 }
167
168 let dst = unsafe { self.ptr.as_ptr().add(self.len) } as *mut u8;
169 let src = items.as_ptr() as *const u8;
170 let bytes = n * core::mem::size_of::<T>();
171
172 // SAFETY: `dst` points to uninitialised space inside the buffer for
173 // exactly `n` elements; `src` is a valid slice of `n` elements.
174 // `T: Copy` means no destructor will be skipped.
175 unsafe { simd::copy_bytes(dst, src, bytes) };
176 self.len += n;
177 }
178
179 /// Panicking growth helper — calls `try_grow` and panics on OOM.
180 #[cold]
181 fn grow(&mut self) { assert!(self.try_grow(), "Vec: out of memory"); }
182
183 /// Attempts to double the buffer capacity.
184 ///
185 /// Returns `true` on success, `false` if the allocator rejected the
186 /// request.
187 fn try_grow(&mut self) -> bool {
188 let new_cap = if self.cap == 0 { 4 } else { self.cap * 2 };
189 let new_layout = match Layout::array::<T>(new_cap) {
190 Ok(l) => l,
191 Err(_) => return false,
192 };
193 let new_ptr = match unsafe { self.alloc.alloc(new_layout) } {
194 Some(p) => p.cast::<T>(),
195 None => return false,
196 };
197
198 if self.cap > 0 {
199 // SAFETY: `new_ptr` is valid for `new_cap` elements; `self.ptr` is
200 // valid for `self.cap` elements. We copy `self.len` initialised
201 // elements; the rest of the new buffer remains uninitialised.
202 // After copying, we release the old buffer with the exact layout
203 // that was used to allocate it.
204 unsafe {
205 simd::copy_bytes(
206 new_ptr.as_ptr() as *mut u8,
207 self.ptr.as_ptr() as *const u8,
208 self.len * core::mem::size_of::<T>(),
209 );
210 let old_layout = Layout::array::<T>(self.cap).unwrap();
211 self.alloc.dealloc(self.ptr.cast(), old_layout);
212 }
213 }
214 self.ptr = new_ptr;
215 self.cap = new_cap;
216 true
217 }
218}
219
220impl ExVec<'_, u8> {
221 /// Fills all initialised bytes with `val` using SIMD.
222 pub fn simd_fill(&mut self, val: u8) {
223 if self.len == 0 { return; }
224 // SAFETY: `ptr` is valid for `len` bytes.
225 unsafe { simd::fill_bytes(self.ptr.as_ptr(), val, self.len) };
226 }
227
228 /// Searches for the first occurrence of `val` in the initialised region.
229 ///
230 /// Returns the byte offset, or `None` if not found.
231 pub fn find_byte(&self, val: u8) -> Option<usize> {
232 if self.len == 0 { return None; }
233 // SAFETY: `ptr` is valid for `len` bytes.
234 unsafe { simd::find_byte(self.ptr.as_ptr(), val, self.len) }
235 }
236
237 /// Calls `f` with the offset of every byte in the initialised region that
238 /// equals `val`.
239 pub fn for_each_byte_match<F: FnMut(usize)>(&self, val: u8, mut f: F) {
240 if self.len == 0 { return; }
241 let ptr = self.ptr.as_ptr();
242 let mut i = 0usize;
243 while let Some(off) = unsafe { simd::find_byte(ptr.add(i), val, self.len - i) } {
244 f(i + off);
245 i += off + 1;
246 if i >= self.len { break; }
247 }
248 }
249
250 /// Appends `additional` bytes all set to `val`, growing the buffer as
251 /// needed.
252 ///
253 /// # Panics
254 ///
255 /// Panics if the allocator fails to grow the buffer.
256 pub fn extend_filled(&mut self, val: u8, additional: usize) {
257 if additional == 0 { return; }
258 while self.cap < self.len + additional {
259 self.grow();
260 }
261 // SAFETY: `ptr + len` is valid for `additional` uninitialised bytes;
262 // writing `val` initialises them.
263 unsafe {
264 simd::fill_bytes(self.ptr.as_ptr().add(self.len), val, additional);
265 self.len += additional;
266 }
267 }
268}
269
270impl<'a> ExVec<'a, MaybeUninit<u8>> {
271 /// Allocates a buffer of `cap` zero-filled `MaybeUninit<u8>` slots and
272 /// returns a fully-populated `ExVec` (`len == cap`).
273 ///
274 /// Returns `None` if allocation fails or `cap` is zero.
275 pub fn with_capacity_zeroed(alloc: &'a dyn Allocator, cap: usize) -> Option<Self> {
276 if cap == 0 { return Some(Self::new(alloc)); }
277 let layout = Layout::array::<MaybeUninit<u8>>(cap).ok()?;
278 // SAFETY: `alloc` returns a pointer valid for `cap` bytes.
279 let ptr = unsafe { alloc.alloc(layout)?.cast::<MaybeUninit<u8>>() };
280 // SAFETY: `ptr` is valid for `cap` bytes; zero-filling `MaybeUninit<u8>`
281 // is always safe because `MaybeUninit` does not have a validity
282 // requirement.
283 unsafe { simd::fill_bytes(ptr.as_ptr() as *mut u8, 0, cap) };
284 Some(Self { ptr, len: cap, cap, alloc, _marker: PhantomData })
285 }
286
287 /// Fills `len` bytes starting at `start` with `val`.
288 ///
289 /// # Panics
290 ///
291 /// Panics if `start + len > self.len`.
292 pub fn fill_range(&mut self, start: usize, len: usize, val: u8) {
293 assert!(start + len <= self.len, "fill_range: out of bounds");
294 // SAFETY: The bounds check above guarantees the target range is within
295 // the initialised region of the buffer.
296 unsafe { simd::fill_bytes(self.ptr.as_ptr().add(start) as *mut u8, val, len) };
297 }
298
299 /// Searches for the first byte equal to `val` in the buffer.
300 ///
301 /// Returns the byte offset, or `None` if not found.
302 pub fn find_byte(&self, val: u8) -> Option<usize> {
303 // SAFETY: `ptr` is valid for `len` bytes.
304 unsafe { simd::find_byte(self.ptr.as_ptr() as *const u8, val, self.len) }
305 }
306}
307
308impl<T> Drop for ExVec<'_, T> {
309 /// Drops all initialised elements and releases the buffer to the allocator.
310 fn drop(&mut self) {
311 if self.cap == 0 { return; }
312 for i in 0..self.len {
313 // SAFETY: Indices `0..len` are initialised.
314 unsafe { ptr::drop_in_place(self.ptr.as_ptr().add(i)) };
315 }
316 let layout = Layout::array::<T>(self.cap).unwrap();
317 // SAFETY: `ptr` was obtained from `alloc` with this exact `layout`.
318 unsafe { self.alloc.dealloc(self.ptr.cast(), layout) };
319 }
320}
321
322impl<T> Deref for ExVec<'_, T> {
323 type Target = [T];
324 fn deref(&self) -> &[T] { self.as_slice() }
325}
326
327impl<T> DerefMut for ExVec<'_, T> {
328 fn deref_mut(&mut self) -> &mut [T] { self.as_mut_slice() }
329}
330
331impl<T> Index<usize> for ExVec<'_, T> {
332 type Output = T;
333 fn index(&self, i: usize) -> &T { &self.as_slice()[i] }
334}
335
336impl<T> IndexMut<usize> for ExVec<'_, T> {
337 fn index_mut(&mut self, i: usize) -> &mut T { &mut self.as_mut_slice()[i] }
338}