Skip to main content

mnemosyne_heap/
branded_vec.rs

1use crate::Heap;
2use crate::brand::BrandedBlock;
3use crate::branded_box::BrandedBox;
4use core::alloc::Layout;
5use core::marker::PhantomData;
6use core::ptr::NonNull;
7use melinoe::{ReadPermit, WritePermit};
8use mnemosyne_core::AllocPolicy;
9use mnemosyne_local::LocalAllocatorSelector;
10use mnemosyne_local::internal::HasSegmentPool;
11
12/// Growth, indexing and element access for [`BrandedVec`].
13pub mod ops;
14/// Standard-trait impls (iteration, comparison, formatting) for
15/// [`BrandedVec`].
16pub mod traits;
17
18/// A dynamically growing array allocated from a `Heap`.
19///
20/// Automatically handles growth and reallocation, dropping all elements on drop.
21pub struct BrandedVec<
22    'brand,
23    'heap,
24    T,
25    P: AllocPolicy = mnemosyne_core::StandardPolicy,
26    B: HasSegmentPool + LocalAllocatorSelector<B> = mnemosyne_backend::MemoryBackendWrapper,
27> {
28    pub(crate) ptr: NonNull<T>,
29    pub(crate) cap: usize,
30    pub(crate) len: usize,
31    pub(crate) heap: &'heap Heap<'brand, P, B>,
32    pub(crate) _non_send_sync: core::marker::PhantomData<*mut ()>,
33}
34
35impl<'brand, 'heap, T, P: AllocPolicy, B: HasSegmentPool + LocalAllocatorSelector<B>>
36    BrandedVec<'brand, 'heap, T, P, B>
37{
38    /// Creates a new empty `BrandedVec` backed by the given `Heap`.
39    #[inline(always)]
40    pub fn new(heap: &'heap Heap<'brand, P, B>) -> Self {
41        Self {
42            ptr: NonNull::dangling(),
43            cap: if core::mem::size_of::<T>() == 0 {
44                usize::MAX
45            } else {
46                0
47            },
48            len: 0,
49            heap,
50            _non_send_sync: core::marker::PhantomData,
51        }
52    }
53
54    /// Creates a new `BrandedVec` with space for at least `capacity` elements.
55    #[inline]
56    pub fn with_capacity<Permit>(
57        heap: &'heap Heap<'brand, P, B>,
58        permit: Permit,
59        capacity: usize,
60    ) -> Option<Self>
61    where
62        Permit: ReadPermit<'brand>,
63    {
64        if capacity == 0 || core::mem::size_of::<T>() == 0 {
65            return Some(Self::new(heap));
66        }
67        let layout = Layout::array::<T>(capacity).ok()?;
68        let block = heap.alloc(permit, layout)?;
69        Some(Self {
70            ptr: block.ptr.cast(),
71            cap: capacity,
72            len: 0,
73            heap,
74            _non_send_sync: core::marker::PhantomData,
75        })
76    }
77
78    /// Converts this vector into a boxed slice, shrinking the memory allocation to fit.
79    #[inline]
80    pub fn into_boxed_slice<Permit>(
81        mut self,
82        token: &mut Permit,
83    ) -> BrandedBox<'brand, 'heap, [T], P, B>
84    where
85        for<'token> &'token mut Permit: WritePermit<'brand>,
86    {
87        if core::mem::size_of::<T>() == 0 {
88            // SAFETY: `T` is zero-sized, so a `[T]` of any length occupies no
89            // bytes; `NonNull::dangling()` is a valid, aligned base for a
90            // zero-sized slice of `self.len` ZST elements. `from_raw_parts_mut`
91            // requires the pointer be non-null and aligned (dangling satisfies
92            // both for a ZST) and the resulting fat pointer is never read/written
93            // for storage. The base pointer is non-null, so `new_unchecked` is
94            // sound. Ownership of the `len` logical elements transfers to the
95            // returned `BrandedBox` (`self` is forgotten below), so no element is
96            // dropped twice.
97            let slice_ptr = unsafe {
98                let raw_slice =
99                    core::slice::from_raw_parts_mut(NonNull::<T>::dangling().as_ptr(), self.len);
100                NonNull::new_unchecked(raw_slice)
101            };
102            let heap = self.heap;
103            core::mem::forget(self);
104            return BrandedBox {
105                ptr: slice_ptr,
106                heap,
107                _non_send_sync: core::marker::PhantomData,
108            };
109        }
110
111        // Best-effort shrink to fit via the shared SSOT helper; a failed realloc
112        // leaves the (larger) block in place and the boxed slice still owns it
113        // correctly, so the `Err` is intentionally ignored here.
114        let _ = self.shrink_to_len(token);
115
116        // SAFETY: for non-ZST `T`, `self.ptr` addresses a live allocation of at
117        // least `self.len` initialized `T` (after the shrink above, `self.cap`
118        // is either unchanged or equal to `self.len`, and `[0, self.len)` is
119        // always the initialized prefix). `slice_from_raw_parts_mut` builds a fat
120        // pointer over exactly those `self.len` elements; `self.ptr` is non-null
121        // (`NonNull`), so `new_unchecked` is sound. Ownership of the elements and
122        // the backing block transfers to the returned `BrandedBox` (`self` is
123        // forgotten below), so the block is freed exactly once.
124        let slice_ptr = unsafe {
125            let raw_slice = core::ptr::slice_from_raw_parts_mut(self.ptr.as_ptr(), self.len);
126            NonNull::new_unchecked(raw_slice)
127        };
128
129        let heap = self.heap;
130        core::mem::forget(self);
131
132        BrandedBox {
133            ptr: slice_ptr,
134            heap,
135            _non_send_sync: core::marker::PhantomData,
136        }
137    }
138
139    /// Converts a `BrandedBox<'brand, 'heap, [T], P, B>` back into a `BrandedVec<'brand, 'heap, T, P, B>`.
140    ///
141    /// This does not allocate or copy.
142    #[inline]
143    pub fn from_boxed_slice(boxed_slice: BrandedBox<'brand, 'heap, [T], P, B>) -> Self {
144        let len = boxed_slice.len();
145        let heap = boxed_slice.heap;
146        let block = boxed_slice.into_raw();
147        Self {
148            // SAFETY: `block.ptr` originates from a `BrandedBox<[T]>`'s
149            // `NonNull<[T]>` and is therefore non-null; reinterpreting the slice
150            // base address as the element pointer `*mut T` preserves
151            // non-nullness (and, for non-ZST `T`, the original allocation's
152            // alignment for `T`), so `new_unchecked` is sound. Ownership of the
153            // block transfers from the consumed box to the new vector with no
154            // copy.
155            ptr: unsafe { NonNull::new_unchecked(block.ptr.as_ptr() as *mut T) },
156            cap: if core::mem::size_of::<T>() == 0 {
157                usize::MAX
158            } else {
159                len
160            },
161            len,
162            heap,
163            _non_send_sync: core::marker::PhantomData,
164        }
165    }
166
167    /// Grows the backing allocation to exactly `new_cap` elements and updates
168    /// `ptr`/`cap` on success. This is the single authoritative grow path shared
169    /// by [`push`](BrandedVec::push) and [`reserve`](BrandedVec::reserve), so the
170    /// alloc-when-empty / realloc-otherwise mechanics cannot drift between them;
171    /// each caller keeps only its own capacity *policy* (push's initial-4
172    /// doubling vs reserve's `max(cap*2, needed)`).
173    ///
174    /// Callers guarantee `T` is non-ZST and `new_cap > self.cap`. On layout
175    /// overflow or allocation failure the vector is left unchanged and `Err(())`
176    /// is returned.
177    #[inline]
178    fn grow_to<Permit>(&mut self, token: &mut Permit, new_cap: usize) -> Result<(), ()>
179    where
180        for<'token> &'token mut Permit: WritePermit<'brand>,
181    {
182        let new_layout = Layout::array::<T>(new_cap).map_err(|_| ())?;
183        if self.cap == 0 {
184            let block = self.heap.alloc(&mut *token, new_layout).ok_or(())?;
185            self.ptr = block.ptr.cast();
186        } else {
187            // `self.cap` was validated when the current allocation was made,
188            // but retain the fallible boundary so a corrupted or future-mutated
189            // capacity cannot turn an allocation failure into undefined behavior.
190            let old_layout = Layout::array::<T>(self.cap).map_err(|_| ())?;
191            let block = BrandedBlock {
192                ptr: self.ptr,
193                _marker: PhantomData,
194            };
195            match self
196                .heap
197                .realloc(token, block, old_layout, new_layout.size())
198            {
199                Ok(Some(new_block)) => self.ptr = new_block.ptr.cast(),
200                Ok(None) => {
201                    debug_assert!(false, "vector growth must not request a zero-sized realloc");
202                    return Err(());
203                }
204                Err(error) => {
205                    self.ptr = error.into_block().ptr.cast();
206                    return Err(());
207                }
208            }
209        }
210        self.cap = new_cap;
211        Ok(())
212    }
213
214    /// Reserves capacity for at least `additional` more elements to be inserted in the vector.
215    ///
216    /// # Errors
217    /// Returns `Err(())` if layout calculations overflow or allocation fails.
218    #[inline]
219    #[expect(clippy::result_unit_err)]
220    pub fn reserve<Permit>(&mut self, token: &mut Permit, additional: usize) -> Result<(), ()>
221    where
222        for<'token> &'token mut Permit: WritePermit<'brand>,
223    {
224        if core::mem::size_of::<T>() == 0 {
225            return Ok(());
226        }
227        let needed = match self.len.checked_add(additional) {
228            Some(n) => n,
229            None => return Err(()),
230        };
231        if needed <= self.cap {
232            return Ok(());
233        }
234        let new_cap = core::cmp::max(self.cap.checked_mul(2).unwrap_or(needed), needed);
235        self.grow_to(token, new_cap)
236    }
237
238    /// Shrinks the capacity of the vector as much as possible.
239    ///
240    /// # Errors
241    /// Returns `Err(())` if allocation fails.
242    #[inline]
243    #[expect(clippy::result_unit_err)]
244    pub fn shrink_to_fit<Permit>(&mut self, token: &mut Permit) -> Result<(), ()>
245    where
246        for<'token> &'token mut Permit: WritePermit<'brand>,
247    {
248        if core::mem::size_of::<T>() == 0 {
249            return Ok(());
250        }
251        self.shrink_to_len(token)
252    }
253
254    /// Shrinks the backing allocation so its capacity equals `self.len` — the
255    /// single authoritative shrink path shared by
256    /// [`shrink_to_fit`](BrandedVec::shrink_to_fit) and
257    /// [`into_boxed_slice`](BrandedVec::into_boxed_slice), so the
258    /// free-when-empty / realloc-to-len mechanics cannot drift between them.
259    ///
260    /// Callers guarantee `T` is non-ZST. A no-op when `cap <= len`; frees the
261    /// block when `len == 0`; otherwise reallocates down to `len` elements.
262    /// Returns `Err(())` only if the shrinking realloc fails, leaving the vector
263    /// valid and unchanged (the over-sized block is retained).
264    #[inline]
265    fn shrink_to_len<Permit>(&mut self, token: &mut Permit) -> Result<(), ()>
266    where
267        for<'token> &'token mut Permit: WritePermit<'brand>,
268    {
269        if self.cap <= self.len {
270            return Ok(());
271        }
272        if self.len == 0 {
273            // SAFETY: reached only with non-ZST `T` and `self.cap > self.len == 0`,
274            // so `self.cap > 0` and `self.ptr` is a live block from `self.heap`
275            // (not the dangling sentinel). No element is initialized, so freeing
276            // drops nothing; `self.ptr`/`self.cap` reset to the dangling sentinel
277            // right after, so the freed block is never reused.
278            unsafe {
279                self.heap.free_raw(self.ptr.as_ptr() as *mut u8);
280            }
281            self.ptr = NonNull::dangling();
282            self.cap = 0;
283            return Ok(());
284        }
285        let old_layout = Layout::array::<T>(self.cap).map_err(|_| ())?;
286        let block = BrandedBlock {
287            ptr: self.ptr,
288            _marker: PhantomData,
289        };
290        let new_size = core::mem::size_of::<T>() * self.len;
291        match self.heap.realloc(token, block, old_layout, new_size) {
292            Ok(Some(new_block)) => {
293                self.ptr = new_block.ptr.cast();
294                self.cap = self.len;
295                Ok(())
296            }
297            Ok(None) => {
298                debug_assert!(false, "nonzero shrink must not free its source block");
299                Err(())
300            }
301            Err(error) => {
302                self.ptr = error.into_block().ptr.cast();
303                Err(())
304            }
305        }
306    }
307}