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}