Skip to main content

zigzag_alloc/collections/
boxed.rs

1//! Heap-allocated single value with an explicit allocator.
2//!
3//! [`ExBox<T>`] represents *unique ownership* of a heap-allocated `T`.  When
4//! dropped it calls `T`'s destructor and returns the backing memory to the
5//! allocator it was created with.
6//!
7//! This is the explicit-allocator analogue of [`std::boxed::Box`].
8
9use core::{
10    alloc::Layout,
11    fmt,
12    mem,
13    ops::{Deref, DerefMut},
14    ptr::{self, NonNull},
15};
16
17use crate::alloc::allocator::Allocator;
18use crate::simd;
19
20/// A pointer type for a single heap-allocated value with an explicit allocator.
21///
22/// # Ownership and Dropping
23///
24/// `ExBox` uniquely owns the value it points to.  On drop:
25/// 1. `T`'s destructor is called via [`drop_in_place`](ptr::drop_in_place).
26/// 2. The backing memory is returned to the allocator.
27///
28/// # Zero-Sized Types
29///
30/// For ZSTs (`size_of::<T>() == 0`) no allocation is performed; `ptr` is set
31/// to `NonNull::dangling()` and `dealloc` is not called on drop.
32///
33/// # Lifetime
34///
35/// The allocator reference `'a` must outlive the `ExBox`.
36pub struct ExBox<'a, T> {
37    /// Non-null pointer to the heap-allocated value.
38    ptr:   NonNull<T>,
39    /// Allocator used to allocate and later free the backing memory.
40    alloc: &'a dyn Allocator,
41}
42
43impl<'a, T> ExBox<'a, T> {
44    /// Allocates memory for `T` and moves `value` into it.
45    ///
46    /// Returns `None` if the allocator fails.
47    ///
48    /// # Example
49    ///
50    /// ```rust,ignore
51    /// let b = ExBox::new(42u64, &sys).unwrap();
52    /// assert_eq!(*b, 42);
53    /// ```
54    pub fn new(value: T, alloc: &'a dyn Allocator) -> Option<Self> {
55        let layout = Layout::new::<T>();
56        let ptr: NonNull<T> = if layout.size() == 0 {
57            // ZST — dangling pointer, never dereferenced.
58            NonNull::dangling()
59        } else {
60            // SAFETY: `layout.size() > 0` as checked above.
61            unsafe { alloc.alloc(layout)?.cast() }
62        };
63        // SAFETY: `ptr` is valid for a write of `T` (or dangling for ZSTs, but
64        // ZST writes are no-ops in Rust).
65        unsafe { ptr.as_ptr().write(value) };
66        Some(Self { ptr, alloc })
67    }
68
69    /// Allocates zero-filled memory for `T`, then moves `value` into it.
70    ///
71    /// The allocation is zeroed *before* `value` is written, which may be
72    /// useful for types with padding bytes that should be deterministic.
73    ///
74    /// Returns `None` if the allocator fails.
75    pub fn new_zeroed(value: T, alloc: &'a dyn Allocator) -> Option<Self> {
76        let layout = Layout::new::<T>();
77        let ptr: NonNull<T> = if layout.size() == 0 {
78            NonNull::dangling()
79        } else {
80            // SAFETY: `layout.size() > 0`.
81            let p = unsafe { alloc.alloc(layout)? };
82            // SAFETY: `p` is valid for `layout.size()` bytes.
83            unsafe { simd::fill_bytes(p.as_ptr(), 0, layout.size()) };
84            p.cast()
85        };
86        // SAFETY: `ptr` is valid for a write of `T`.
87        unsafe { ptr.as_ptr().write(value) };
88        Some(Self { ptr, alloc })
89    }
90
91    /// Consumes the box, returns the inner value, and deallocates the backing
92    /// memory.
93    ///
94    /// # Implementation Note
95    ///
96    /// We read the value out of the pointer and then call
97    /// [`mem::forget`] on the `ExBox` to prevent the `Drop` implementation
98    /// from running a second destructor or freeing the (already freed) memory.
99    pub fn unbox(b: Self) -> T {
100        // SAFETY: `b.ptr` points to a valid, initialised `T`.  Reading it
101        // transfers ownership.  We call `mem::forget(b)` immediately after so
102        // `Drop` does not run — preventing double-drop and double-free.
103        let value = unsafe { b.ptr.as_ptr().read() };
104        let layout = Layout::new::<T>();
105        if layout.size() > 0 {
106            // SAFETY: `b.ptr` was obtained from `b.alloc` with this `layout`.
107            unsafe { b.alloc.dealloc(b.ptr.cast(), layout) };
108        }
109        mem::forget(b);
110        value
111    }
112
113    /// Zeroes all bytes of the allocation in-place (without moving or dropping
114    /// `T`).
115    ///
116    /// # Safety
117    ///
118    /// After this call, the memory backing `*b` contains all zeros.  If `T`
119    /// has any validity invariants (e.g. non-null pointers, non-zero
120    /// discriminants) those invariants will be violated.  Accessing `*b` after
121    /// calling `wipe` is undefined behaviour unless `T` is a type for which
122    /// all-zeros is a valid representation.
123    pub unsafe fn wipe(b: &mut Self) {
124        let layout = Layout::new::<T>();
125        if layout.size() > 0 {
126            // SAFETY: Caller accepts responsibility for the consequences of
127            // zeroing the bytes.  The pointer is valid for `layout.size()` bytes.
128            unsafe { simd::fill_bytes(b.ptr.as_ptr() as *mut u8, 0, layout.size()) };
129        }
130    }
131
132    /// Returns a raw const pointer to the contained value.
133    #[inline]
134    pub fn as_ptr(b: &Self) -> *const T { b.ptr.as_ptr() }
135
136    /// Returns a raw mutable pointer to the contained value.
137    #[inline]
138    pub fn as_mut_ptr(b: &mut Self) -> *mut T { b.ptr.as_ptr() }
139}
140
141impl<T> Drop for ExBox<'_, T> {
142    /// Runs `T`'s destructor and returns the backing memory to the allocator.
143    fn drop(&mut self) {
144        unsafe {
145            // SAFETY: `self.ptr` is valid and initialised — we have unique
146            // ownership of the value, so running the destructor is correct.
147            ptr::drop_in_place(self.ptr.as_ptr());
148            let layout = Layout::new::<T>();
149            if layout.size() > 0 {
150                // SAFETY: `self.ptr` was allocated from `self.alloc` with this
151                // exact `layout`, and `drop_in_place` above did not free it.
152                self.alloc.dealloc(self.ptr.cast(), layout);
153            }
154        }
155    }
156}
157
158impl<T> Deref for ExBox<'_, T> {
159    type Target = T;
160    fn deref(&self) -> &T {
161        // SAFETY: `ptr` is valid and initialised; we hold shared access via `&self`.
162        unsafe { self.ptr.as_ref() }
163    }
164}
165
166impl<T> DerefMut for ExBox<'_, T> {
167    fn deref_mut(&mut self) -> &mut T {
168        // SAFETY: `ptr` is valid and initialised; `&mut self` guarantees unique access.
169        unsafe { self.ptr.as_mut() }
170    }
171}
172
173impl<T: fmt::Debug> fmt::Debug for ExBox<'_, T> {
174    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
175        fmt::Debug::fmt(&**self, f)
176    }
177}
178
179impl<T: fmt::Display> fmt::Display for ExBox<'_, T> {
180    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
181        fmt::Display::fmt(&**self, f)
182    }
183}
184
185impl<T: PartialEq> PartialEq for ExBox<'_, T> {
186    fn eq(&self, other: &Self) -> bool { **self == **other }
187}