untyped_box/
impl.rs

1use core::{alloc::Layout, any::type_name, mem::MaybeUninit, ptr::NonNull};
2
3use crate::alloc_shim::{AllocError, Allocator, Global};
4
5/// An allocation is management representation of some allocated memory.
6///
7/// For the most part, this behaves like a lower-level (untyped) cousin of a `Box`.
8/// The memory backing this allocation is deallocated when the allocation is dropped.
9/// In contrast, no validity or initialization state of the memory is implied by
10/// existance of an [Allocation].
11pub struct Allocation<A: Allocator = Global> {
12    // TODO: should be a Unique pointer!
13    ptr: NonNull<u8>,
14    layout: Layout,
15    alloc: A,
16}
17
18// TODO: There is a bit of a mismatch here. In essence, we are losing information.
19// For example, requesting an allocation for some `Layout::new::<T>()` that results in the allocator
20// giving us more memory than we asked for might make later checks when trying to convert to a `Box`
21// fail on size mismatch.
22// We might have to blow up the allocation struct to reconstruct [Memory fitting] information.
23// [Memory fitting]: https://doc.rust-lang.org/nightly/alloc/alloc/trait.Allocator.html#memory-fitting
24fn match_allocated_size(ptr: NonNull<[u8]>, layout: Layout) -> (NonNull<u8>, Layout) {
25    let actual_layout = unsafe { Layout::from_size_align_unchecked(ptr.len(), layout.align()) };
26    debug_assert!(actual_layout.size() >= layout.size());
27    (ptr.cast(), actual_layout)
28}
29fn allocate(alloc: &impl Allocator, layout: Layout) -> Result<(NonNull<u8>, Layout), AllocError> {
30    let ptr = alloc.allocate(layout)?;
31    Ok(match_allocated_size(ptr, layout))
32}
33fn allocate_zeroed(
34    alloc: &impl Allocator,
35    layout: Layout,
36) -> Result<(NonNull<u8>, Layout), AllocError> {
37    let ptr = alloc.allocate_zeroed(layout)?;
38    Ok(match_allocated_size(ptr, layout))
39}
40unsafe fn grow(
41    alloc: &impl Allocator,
42    ptr: NonNull<u8>,
43    old_layout: Layout,
44    new_layout: Layout,
45) -> Result<(NonNull<u8>, Layout), AllocError> {
46    let ptr = alloc.grow(ptr, old_layout, new_layout)?;
47    Ok(match_allocated_size(ptr, new_layout))
48}
49unsafe fn grow_zeroed(
50    alloc: &impl Allocator,
51    ptr: NonNull<u8>,
52    old_layout: Layout,
53    new_layout: Layout,
54) -> Result<(NonNull<u8>, Layout), AllocError> {
55    let ptr = alloc.grow_zeroed(ptr, old_layout, new_layout)?;
56    Ok(match_allocated_size(ptr, new_layout))
57}
58unsafe fn shrink(
59    alloc: &impl Allocator,
60    ptr: NonNull<u8>,
61    old_layout: Layout,
62    new_layout: Layout,
63) -> Result<(NonNull<u8>, Layout), AllocError> {
64    let ptr = alloc.shrink(ptr, old_layout, new_layout)?;
65    Ok(match_allocated_size(ptr, new_layout))
66}
67
68/// Methods for the global allocator
69impl Allocation {
70    /// Allocate new memory for the given layout.
71    ///
72    /// The pointer backing the allocation is valid for reads and writes of `layout.size()` bytes and this
73    /// memory region does not alias any other existing allocation.
74    ///
75    /// The pointer is guaranteed to be aligned to `layout.align()` but several systems align memory more
76    /// lax when a small alignment is requested.
77    ///
78    /// Memory is not initialized or zeroed, try [`Self::zeroed`] instead.
79    ///
80    /// # Panics
81    ///
82    /// This calls [`alloc::alloc::handle_alloc_error`] when no memory could be allocated, which can panic.
83    /// See [`Self::try_new_in`] for a version that returns an error instead.
84    // Forwards to alloc, handles layout.size() == 0 with a dangling ptr
85    pub fn new(layout: Layout) -> Self {
86        Self::new_in(layout, Global)
87    }
88    /// Allocate new memory for the given layout.
89    ///
90    /// Same as [`Self::new`] but returns an error when memory could not be allocated.
91    pub fn try_new(layout: Layout) -> Result<Self, AllocError> {
92        Self::try_new_in(layout, Global)
93    }
94    /// Allocate new zeroed-out memory for the given layout.
95    ///
96    /// # Panics
97    ///
98    /// This calls [`alloc::alloc::handle_alloc_error`] when no memory could be allocated, which can panic.
99    /// See [`Self::try_zeroed_in`] for a version that returns an error instead.
100    pub fn zeroed(layout: Layout) -> Self {
101        Self::zeroed_in(layout, Global)
102    }
103    /// Allocate new zeroed-out memory for the given layout.
104    ///
105    /// Same as [`Self::zeroed`] but returns an error when memory could not be allocated.
106    pub fn try_zeroed(layout: Layout) -> Result<Self, AllocError> {
107        Self::try_zeroed_in(layout, Global)
108    }
109    /// Split the allocation into its raw parts.
110    ///
111    /// Deallocating the allocation is the responsibility of the caller. The returned
112    /// pointer can be passed to [`alloc::alloc::dealloc`] if the returned layout indicates `size() > 0`.
113    /// If the allocated memory is 0 sized, the pointer does not need to be deallocated.
114    ///
115    /// See also [`Self::into_parts_with_alloc`] for an allocator-aware version.
116    pub fn into_parts(self) -> (NonNull<u8>, Layout) {
117        let (ptr, layout, _) = Self::into_parts_with_alloc(self);
118        (ptr, layout)
119    }
120    /// Constructs an [`Allocation`] from a pointer and layout information.
121    ///
122    /// # Safety
123    ///
124    /// The pointer must point to [*currently-allocated*] memory from the global allocator, and `layout`
125    /// was used to allocate that memory.
126    ///
127    /// [*currently-allocated*]: Allocator#currently-allocated-memory
128    pub unsafe fn from_parts(ptr: NonNull<u8>, layout: Layout) -> Self {
129        Self::from_parts_in(ptr, layout, Global)
130    }
131}
132/// Common methods
133impl<A: Allocator> Allocation<A> {
134    /// Gets a pointer to the allocation.
135    ///
136    /// The pointer is always aligned to the alignment of the layout indicated by [Self::layout] or the requested layout
137    /// indicated on allocation, whichever is more strict.
138    ///
139    /// The pointer can be used to read and write memory in this allocation until it is [reallocated](Self::realloc),
140    /// dropped or the memory is reclaimed manually (e.g. after converting [`into_parts`](Self::into_parts)).
141    ///
142    /// In particular, the pointer does not in itself materialize a reference to the underlying storage for the purpose of the aliasing model.
143    pub fn as_ptr<T>(&self) -> NonNull<T> {
144        self.ptr.cast()
145    }
146    /// View the underlying storage as a possibly uninitialized `T`.
147    ///
148    /// # Panics
149    ///
150    /// If the allocation is too small, or not aligned enough to contain a `T`.
151    pub fn as_uninit_ref<T>(&self) -> &MaybeUninit<T> {
152        assert!(
153            self.layout.size() >= size_of::<T>(),
154            "allocation too small to represent a {}",
155            type_name::<T>()
156        );
157        assert!(
158            self.layout.align() >= align_of::<T>(),
159            "allocation not aligned for a {}",
160            type_name::<T>()
161        );
162        unsafe { &*self.ptr.as_ptr().cast() }
163    }
164    /// View the underlying storage as a possibly uninitialized `T`.
165    ///
166    /// # Panics
167    ///
168    /// If the allocation is too small, or not aligned enough to contain a `T`.
169    pub fn as_uninit_mut<T>(&mut self) -> &mut MaybeUninit<T> {
170        assert!(
171            self.layout.size() >= size_of::<T>(),
172            "allocation too small to represent a {}",
173            type_name::<T>()
174        );
175        assert!(
176            self.layout.align() >= align_of::<T>(),
177            "allocation not aligned for a {}",
178            type_name::<T>()
179        );
180        unsafe { &mut *self.ptr.as_ptr().cast() }
181    }
182    /// View the allocation as a pointer to a slice of possibly uninitialized bytes.
183    ///
184    /// The caller is responsible for checking lifetimes when convert to a reference.
185    ///
186    /// The pointer can be used to read and write memory in this allocation until it is [reallocated](Self::realloc),
187    /// dropped or the memory is reclaimed manually (e.g. after converting [`into_parts`](Self::into_parts)).
188    ///
189    /// Like [`as_ptr`](Self::as_ptr), this does not materialize a reference to the underlying storage for the purpose of the aliasing model.
190    /// Hence, these two methods can be intermixed.
191    pub fn as_slice(&self) -> NonNull<[MaybeUninit<u8>]> {
192        let ptr = core::ptr::slice_from_raw_parts_mut(
193            self.ptr.as_ptr().cast::<MaybeUninit<u8>>(),
194            self.layout.size(),
195        );
196        unsafe { NonNull::new_unchecked(ptr) }
197    }
198    /// Reallocates memory to a new layout.
199    ///
200    /// If the newly requested layout is larger than the currently allocated layout, existing (possibly uninitialized) bytes are preserved.
201    /// Newly allocated bytes are uninitialized.
202    ///
203    /// Any pointers to the managed memory are invalidated on return.
204    ///
205    /// # Panics
206    ///
207    /// This calls [`alloc::alloc::handle_alloc_error`] when no memory could be allocated, which can panic. In this case, pointers are still valid.
208    /// See [`Self::try_realloc`] for a version that returns an error instead.
209    // Calls either grow or shrink, compares against stored layout
210    pub fn realloc(&mut self, new_layout: Layout) {
211        let () = self
212            .try_realloc(new_layout)
213            .unwrap_or_else(|AllocError| alloc::alloc::handle_alloc_error(new_layout));
214    }
215    /// Reallocates memory to a new layout.
216    ///
217    /// If the newly requested layout is larger than the currently allocated layout, existing (possibly uninitialized) bytes are preserved.
218    /// Newly allocated bytes are zeroed.
219    ///
220    /// Any pointers to the managed memory are invalidated on return.
221    ///
222    /// # Panics
223    ///
224    /// This calls [`alloc::alloc::handle_alloc_error`] when no memory could be allocated, which can panic. In this case, pointers are still valid.
225    /// See [`Self::try_realloc_zeroed`] for a version that returns an error instead.
226    pub fn realloc_zeroed(&mut self, new_layout: Layout) {
227        let () = self
228            .try_realloc_zeroed(new_layout)
229            .unwrap_or_else(|AllocError| alloc::alloc::handle_alloc_error(new_layout));
230    }
231    /// Get the layout of the underlying allocation.
232    ///
233    /// This layout is guaranteed to be at least as large as previously requested from [`new`](Self::new) or [`realloc`](Self::realloc) and
234    /// at least as strictly aligned, but might indicate more available memory.
235    pub fn layout(&self) -> Layout {
236        self.layout
237    }
238}
239/// Methods using the allocator-api or shim
240impl<A: Allocator> Allocation<A> {
241    /// Allocate new memory for the given layout in a given allocator.
242    ///
243    /// The pointer backing the allocation is valid for reads and writes of `layout.size()` bytes and this
244    /// memory region does not alias any other existing allocation.
245    ///
246    /// The pointer is guaranteed to be aligned to `layout.align()` but several systems align memory more
247    /// lax when a small alignment is requested.
248    ///
249    /// Memory is not initialized or zeroed, try [`Self::zeroed_in`] instead.
250    ///
251    /// # Panics
252    ///
253    /// This calls [`alloc::alloc::handle_alloc_error`] when no memory could be allocated, which can panic.
254    /// See [`Self::try_new_in`] for a version that returns an error instead.
255    pub fn new_in(layout: Layout, alloc: A) -> Self {
256        Self::try_new_in(layout, alloc)
257            .unwrap_or_else(|AllocError| alloc::alloc::handle_alloc_error(layout))
258    }
259    /// Allocate new memory for the given layout in a given allocator.
260    ///
261    /// Returns an error when no memory could be allocated.
262    pub fn try_new_in(layout: Layout, alloc: A) -> Result<Self, AllocError> {
263        let (ptr, layout) = allocate(&alloc, layout)?;
264        Ok(Self { ptr, layout, alloc })
265    }
266    /// Allocate new zeroed-out memory for the given layout in a given allocator.
267    ///
268    /// # Panics
269    ///
270    /// This calls [`alloc::alloc::handle_alloc_error`] when no memory could be allocated, which can panic.
271    /// See [`Self::try_zeroed_in`] for a version that returns an error instead.
272    pub fn zeroed_in(layout: Layout, alloc: A) -> Self {
273        Self::try_zeroed_in(layout, alloc)
274            .unwrap_or_else(|AllocError| alloc::alloc::handle_alloc_error(layout))
275    }
276    /// Allocate new zeroed-out memory for the given layout in a given allocator.
277    ///
278    /// Returns an error when no memory could be allocated.
279    pub fn try_zeroed_in(layout: Layout, alloc: A) -> Result<Self, AllocError> {
280        let (ptr, layout) = allocate_zeroed(&alloc, layout)?;
281        Ok(Self { ptr, layout, alloc })
282    }
283    /// Split the allocation into its raw parts including the allocator.
284    ///
285    /// Deallocating the allocation is the responsibility of the caller. The returned
286    /// pointer can be passed to `alloc.deallocate()`.
287    pub fn into_parts_with_alloc(self) -> (NonNull<u8>, Layout, A) {
288        let me = core::mem::ManuallyDrop::new(self);
289        let alloc = unsafe { core::ptr::read(&me.alloc) };
290        (me.ptr, me.layout, alloc)
291    }
292    /// Constructs an [`Allocation`] from a pointer and layout information in the given allocator.
293    ///
294    /// # Safety
295    ///
296    /// The pointer must point to [*currently-allocated*] memory from the given allocator, and `layout`
297    /// [*fits*] that memory.
298    ///
299    /// [*currently-allocated*]: Allocator#currently-allocated-memory
300    /// [*fits*]: Allocator#memory-fitting
301    pub unsafe fn from_parts_in(ptr: NonNull<u8>, layout: Layout, alloc: A) -> Self {
302        Self { ptr, layout, alloc }
303    }
304    /// Reallocates memory to a new layout.
305    ///
306    /// Returns an error when the memory could not be reallocated. In this case, any previously derived
307    /// pointers remain valid and no memory is deallocated.
308    ///
309    /// # See also
310    ///
311    /// [`Self::realloc`] for more disuccion about the memory contents after reallocation.
312    pub fn try_realloc(&mut self, new_layout: Layout) -> Result<(), AllocError> {
313        if new_layout == self.layout {
314            return Ok(());
315        }
316        // Prefer grow to shrink when all we do is change alignment
317        if new_layout.size() >= self.layout.size() {
318            (self.ptr, self.layout) =
319                unsafe { grow(&self.alloc, self.ptr, self.layout, new_layout)? };
320            Ok(())
321        } else {
322            (self.ptr, self.layout) =
323                unsafe { shrink(&self.alloc, self.ptr, self.layout, new_layout)? };
324            Ok(())
325        }
326    }
327    /// Reallocates memory to a new layout.
328    ///
329    /// Returns an error when the memory could not be reallocated. In this case, any previously derived
330    /// pointers remain valid and no memory is deallocated.
331    ///
332    /// # See also
333    ///
334    /// [`Self::realloc_zeroed`] for more disuccion about the memory contents after reallocation.
335    pub fn try_realloc_zeroed(&mut self, new_layout: Layout) -> Result<(), AllocError> {
336        if new_layout == self.layout {
337            return Ok(());
338        }
339        // Prefer grow to shrink when all we do is change alignment
340        if new_layout.size() >= self.layout.size() {
341            (self.ptr, self.layout) =
342                unsafe { grow_zeroed(&self.alloc, self.ptr, self.layout, new_layout)? };
343            Ok(())
344        } else {
345            (self.ptr, self.layout) =
346                unsafe { shrink(&self.alloc, self.ptr, self.layout, new_layout)? };
347            Ok(())
348        }
349    }
350}
351
352impl<A: Allocator> Drop for Allocation<A> {
353    fn drop(&mut self) {
354        unsafe {
355            self.alloc.deallocate(self.ptr, self.layout);
356        }
357    }
358}
359
360unsafe impl<A: Allocator + Sync> Sync for Allocation<A> {}
361unsafe impl<A: Allocator + Send> Send for Allocation<A> {}