bump_scope/
alloc.rs

1//! Memory allocation APIs.
2//!
3//! This crate's [`Allocator`], [`AllocError`] and [`Global`] resemble the types and traits from
4//! the nightly allocator api of the standard library (3528a5b 2025-05-11).
5//!
6//! `bump-scope` provides compatibility with the allocator apis of:
7//! - the nightly standard library via the feature `nightly-allocator-api`
8//! - `allocator_api2` version 0.2 via the feature `allocator-api2-02`
9//! - `allocator_api2` version 0.3 via the feature `allocator-api2-03`
10//!
11//! `Bump` and `Bump(Scope)` will implement those foreign `Allocator` traits when the respective feature is enabled.
12//! You can also use implementors of their `Allocator` trait as base allocators via the [compat] wrapper types.
13//!
14//! You can convert between this crate's `AllocError` and foreign one's via the `From` and `Into` traits.
15
16#[cfg(feature = "alloc")]
17mod global;
18#[cfg(feature = "std")]
19mod system;
20
21use core::{
22    alloc::Layout,
23    error::Error,
24    fmt,
25    ptr::{self, NonNull},
26};
27
28use crate::polyfill::non_null;
29
30#[cfg(feature = "alloc")]
31pub use global::Global;
32
33/// Contains wrappers that makes implementors of foreign `Allocator` traits
34/// implement this crate's [`Allocator`] and vice versa.
35///
36/// Note that the bump allocator itself already implements foreign `Allocator` traits, so you
37/// generally only need this for a base allocator.
38pub mod compat {
39    #[cfg(feature = "allocator-api2-02")]
40    pub use crate::features::allocator_api2_02::AllocatorApi2V02Compat;
41    #[cfg(feature = "allocator-api2-03")]
42    pub use crate::features::allocator_api2_03::AllocatorApi2V03Compat;
43    #[cfg(all(feature = "alloc", feature = "nightly-allocator-api"))]
44    pub use crate::features::nightly_allocator_api::AllocatorNightlyCompat;
45}
46
47/// The `AllocError` error indicates an allocation failure
48/// that may be due to resource exhaustion or to
49/// something wrong when combining the given input arguments with this
50/// allocator.
51#[derive(Copy, Clone, PartialEq, Eq, Debug)]
52pub struct AllocError;
53
54impl Error for AllocError {}
55
56// (we need this for downstream impl of trait Error)
57impl fmt::Display for AllocError {
58    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59        f.write_str("memory allocation failed")
60    }
61}
62
63/// An implementation of `Allocator` can allocate, grow, shrink, and deallocate arbitrary blocks of
64/// data described via [`Layout`][].
65///
66/// `Allocator` is designed to be implemented on ZSTs, references, or smart pointers.
67/// An allocator for `MyAlloc([u8; N])` cannot be moved, without updating the pointers to the
68/// allocated memory.
69///
70/// In contrast to [`GlobalAlloc`][alloc_crate::alloc::GlobalAlloc], `Allocator` allows zero-sized allocations. If an underlying
71/// allocator does not support this (like jemalloc) or responds by returning a null pointer
72/// (such as `libc::malloc`), this must be caught by the implementation.
73///
74/// ### Currently allocated memory
75///
76/// Some of the methods require that a memory block is *currently allocated* by an allocator.
77/// This means that:
78///  * the starting address for that memory block was previously
79///    returned by [`allocate`], [`grow`], or [`shrink`], and
80///  * the memory block has not subsequently been deallocated.
81///
82/// A memory block is deallocated by a call to [`deallocate`],
83/// or by a call to [`grow`] or [`shrink`] that returns `Ok`.
84/// A call to `grow` or `shrink` that returns `Err`,
85/// does not deallocate the memory block passed to it.
86///
87/// [`allocate`]: Allocator::allocate
88/// [`grow`]: Allocator::grow
89/// [`shrink`]: Allocator::shrink
90/// [`deallocate`]: Allocator::deallocate
91///
92/// ### Memory fitting
93///
94/// Some of the methods require that a `layout` *fit* a memory block or vice versa. This means that the
95/// following conditions must hold:
96///  * the memory block must be *currently allocated* with alignment of [`layout.align()`], and
97///  * [`layout.size()`] must fall in the range `min ..= max`, where:
98///    - `min` is the size of the layout used to allocate the block, and
99///    - `max` is the actual size returned from [`allocate`], [`grow`], or [`shrink`].
100///
101/// [`layout.align()`]: Layout::align
102/// [`layout.size()`]: Layout::size
103///
104/// # Safety
105///
106/// Memory blocks that are [*currently allocated*] by an allocator,
107/// must point to valid memory, and retain their validity while until either:
108///  - the memory block is deallocated, or
109///  - the allocator is dropped.
110///
111/// Copying, cloning, or moving the allocator must not invalidate memory blocks returned from it.
112/// A copied or cloned allocator must behave like the original allocator.
113///
114/// A memory block which is [*currently allocated*] may be passed to
115/// any method of the allocator that accepts such an argument.
116///
117/// [*currently allocated*]: #currently-allocated-memory
118pub unsafe trait Allocator {
119    /// Attempts to allocate a block of memory.
120    ///
121    /// On success, returns a [`NonNull<[u8]>`][NonNull] meeting the size and alignment guarantees of `layout`.
122    ///
123    /// The returned block may have a larger size than specified by `layout.size()`, and may or may
124    /// not have its contents initialized.
125    ///
126    /// The returned block of memory remains valid as long as it is [*currently allocated*] and the shorter of:
127    ///   - the borrow-checker lifetime of the allocator type itself.
128    ///   - as long as at the allocator and all its clones has not been dropped.
129    ///
130    /// # Errors
131    ///
132    /// Returning `Err` indicates that either memory is exhausted or `layout` does not meet
133    /// allocator's size or alignment constraints.
134    ///
135    /// Implementations are encouraged to return `Err` on memory exhaustion rather than panicking or
136    /// aborting, but this is not a strict requirement. (Specifically: it is *legal* to implement
137    /// this trait atop an underlying native allocation library that aborts on memory exhaustion.)
138    ///
139    /// Clients wishing to abort computation in response to an allocation error are encouraged to
140    /// call the [`handle_alloc_error`] function, rather than directly invoking `panic!` or similar.
141    ///
142    /// [`handle_alloc_error`]: ../../alloc/alloc/fn.handle_alloc_error.html
143    fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError>;
144
145    /// Behaves like `allocate`, but also ensures that the returned memory is zero-initialized.
146    ///
147    /// # Errors
148    ///
149    /// Returning `Err` indicates that either memory is exhausted or `layout` does not meet
150    /// allocator's size or alignment constraints.
151    ///
152    /// Implementations are encouraged to return `Err` on memory exhaustion rather than panicking or
153    /// aborting, but this is not a strict requirement. (Specifically: it is *legal* to implement
154    /// this trait atop an underlying native allocation library that aborts on memory exhaustion.)
155    ///
156    /// Clients wishing to abort computation in response to an allocation error are encouraged to
157    /// call the [`handle_alloc_error`] function, rather than directly invoking `panic!` or similar.
158    ///
159    /// [`handle_alloc_error`]: ../../alloc/alloc/fn.handle_alloc_error.html
160    fn allocate_zeroed(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
161        let ptr = self.allocate(layout)?;
162        // SAFETY: `alloc` returns a valid memory block
163        unsafe { non_null::as_non_null_ptr(ptr).as_ptr().write_bytes(0, ptr.len()) }
164        Ok(ptr)
165    }
166
167    /// Deallocates the memory referenced by `ptr`.
168    ///
169    /// # Safety
170    ///
171    /// * `ptr` must denote a block of memory [*currently allocated*] via this allocator, and
172    /// * `layout` must [*fit*] that block of memory.
173    ///
174    /// [*currently allocated*]: #currently-allocated-memory
175    /// [*fit*]: #memory-fitting
176    unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout);
177
178    /// Attempts to extend the memory block.
179    ///
180    /// Returns a new [`NonNull<[u8]>`][NonNull] containing a pointer and the actual size of the allocated
181    /// memory. The pointer is suitable for holding data described by `new_layout`. To accomplish
182    /// this, the allocator may extend the allocation referenced by `ptr` to fit the new layout.
183    ///
184    /// If this returns `Ok`, then ownership of the memory block referenced by `ptr` has been
185    /// transferred to this allocator. Any access to the old `ptr` is Undefined Behavior, even if the
186    /// allocation was grown in-place. The newly returned pointer is the only valid pointer
187    /// for accessing this memory now.
188    ///
189    /// If this method returns `Err`, then ownership of the memory block has not been transferred to
190    /// this allocator, and the contents of the memory block are unaltered.
191    ///
192    /// # Safety
193    ///
194    /// * `ptr` must denote a block of memory [*currently allocated*] via this allocator.
195    /// * `old_layout` must [*fit*] that block of memory (The `new_layout` argument need not fit it.).
196    /// * `new_layout.size()` must be greater than or equal to `old_layout.size()`.
197    ///
198    /// Note that `new_layout.align()` need not be the same as `old_layout.align()`.
199    ///
200    /// [*currently allocated*]: #currently-allocated-memory
201    /// [*fit*]: #memory-fitting
202    ///
203    /// # Errors
204    ///
205    /// Returns `Err` if the new layout does not meet the allocator's size and alignment
206    /// constraints of the allocator, or if growing otherwise fails.
207    ///
208    /// Implementations are encouraged to return `Err` on memory exhaustion rather than panicking or
209    /// aborting, but this is not a strict requirement. (Specifically: it is *legal* to implement
210    /// this trait atop an underlying native allocation library that aborts on memory exhaustion.)
211    ///
212    /// Clients wishing to abort computation in response to an allocation error are encouraged to
213    /// call the [`handle_alloc_error`] function, rather than directly invoking `panic!` or similar.
214    ///
215    /// [`handle_alloc_error`]: ../../alloc/alloc/fn.handle_alloc_error.html
216    unsafe fn grow(&self, ptr: NonNull<u8>, old_layout: Layout, new_layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
217        debug_assert!(
218            new_layout.size() >= old_layout.size(),
219            "`new_layout.size()` must be greater than or equal to `old_layout.size()`"
220        );
221
222        let new_ptr = self.allocate(new_layout)?;
223
224        // SAFETY: because `new_layout.size()` must be greater than or equal to
225        // `old_layout.size()`, both the old and new memory allocation are valid for reads and
226        // writes for `old_layout.size()` bytes. Also, because the old allocation wasn't yet
227        // deallocated, it cannot overlap `new_ptr`. Thus, the call to `copy_nonoverlapping` is
228        // safe. The safety contract for `dealloc` must be upheld by the caller.
229        unsafe {
230            ptr::copy_nonoverlapping(ptr.as_ptr(), non_null::as_mut_ptr(new_ptr), old_layout.size());
231            self.deallocate(ptr, old_layout);
232        }
233
234        Ok(new_ptr)
235    }
236
237    /// Behaves like `grow`, but also ensures that the new contents are set to zero before being
238    /// returned.
239    ///
240    /// The memory block will contain the following contents after a successful call to
241    /// `grow_zeroed`:
242    ///   * Bytes `0..old_layout.size()` are preserved from the original allocation.
243    ///   * Bytes `old_layout.size()..old_size` will either be preserved or zeroed, depending on
244    ///     the allocator implementation. `old_size` refers to the size of the memory block prior
245    ///     to the `grow_zeroed` call, which may be larger than the size that was originally
246    ///     requested when it was allocated.
247    ///   * Bytes `old_size..new_size` are zeroed. `new_size` refers to the size of the memory
248    ///     block returned by the `grow_zeroed` call.
249    ///
250    /// # Safety
251    ///
252    /// * `ptr` must denote a block of memory [*currently allocated*] via this allocator.
253    /// * `old_layout` must [*fit*] that block of memory (The `new_layout` argument need not fit it.).
254    /// * `new_layout.size()` must be greater than or equal to `old_layout.size()`.
255    ///
256    /// Note that `new_layout.align()` need not be the same as `old_layout.align()`.
257    ///
258    /// [*currently allocated*]: #currently-allocated-memory
259    /// [*fit*]: #memory-fitting
260    ///
261    /// # Errors
262    ///
263    /// Returns `Err` if the new layout does not meet the allocator's size and alignment
264    /// constraints of the allocator, or if growing otherwise fails.
265    ///
266    /// Implementations are encouraged to return `Err` on memory exhaustion rather than panicking or
267    /// aborting, but this is not a strict requirement. (Specifically: it is *legal* to implement
268    /// this trait atop an underlying native allocation library that aborts on memory exhaustion.)
269    ///
270    /// Clients wishing to abort computation in response to an allocation error are encouraged to
271    /// call the [`handle_alloc_error`] function, rather than directly invoking `panic!` or similar.
272    ///
273    /// [`handle_alloc_error`]: ../../alloc/alloc/fn.handle_alloc_error.html
274    unsafe fn grow_zeroed(
275        &self,
276        ptr: NonNull<u8>,
277        old_layout: Layout,
278        new_layout: Layout,
279    ) -> Result<NonNull<[u8]>, AllocError> {
280        debug_assert!(
281            new_layout.size() >= old_layout.size(),
282            "`new_layout.size()` must be greater than or equal to `old_layout.size()`"
283        );
284
285        let new_ptr = self.allocate_zeroed(new_layout)?;
286
287        // SAFETY: because `new_layout.size()` must be greater than or equal to
288        // `old_layout.size()`, both the old and new memory allocation are valid for reads and
289        // writes for `old_layout.size()` bytes. Also, because the old allocation wasn't yet
290        // deallocated, it cannot overlap `new_ptr`. Thus, the call to `copy_nonoverlapping` is
291        // safe. The safety contract for `dealloc` must be upheld by the caller.
292        unsafe {
293            ptr::copy_nonoverlapping(ptr.as_ptr(), non_null::as_mut_ptr(new_ptr), old_layout.size());
294            self.deallocate(ptr, old_layout);
295        }
296
297        Ok(new_ptr)
298    }
299
300    /// Attempts to shrink the memory block.
301    ///
302    /// Returns a new [`NonNull<[u8]>`][NonNull] containing a pointer and the actual size of the allocated
303    /// memory. The pointer is suitable for holding data described by `new_layout`. To accomplish
304    /// this, the allocator may shrink the allocation referenced by `ptr` to fit the new layout.
305    ///
306    /// If this returns `Ok`, then ownership of the memory block referenced by `ptr` has been
307    /// transferred to this allocator. Any access to the old `ptr` is Undefined Behavior, even if the
308    /// allocation was shrunk in-place. The newly returned pointer is the only valid pointer
309    /// for accessing this memory now.
310    ///
311    /// If this method returns `Err`, then ownership of the memory block has not been transferred to
312    /// this allocator, and the contents of the memory block are unaltered.
313    ///
314    /// # Safety
315    ///
316    /// * `ptr` must denote a block of memory [*currently allocated*] via this allocator.
317    /// * `old_layout` must [*fit*] that block of memory (The `new_layout` argument need not fit it.).
318    /// * `new_layout.size()` must be smaller than or equal to `old_layout.size()`.
319    ///
320    /// Note that `new_layout.align()` need not be the same as `old_layout.align()`.
321    ///
322    /// [*currently allocated*]: #currently-allocated-memory
323    /// [*fit*]: #memory-fitting
324    ///
325    /// # Errors
326    ///
327    /// Returns `Err` if the new layout does not meet the allocator's size and alignment
328    /// constraints of the allocator, or if shrinking otherwise fails.
329    ///
330    /// Implementations are encouraged to return `Err` on memory exhaustion rather than panicking or
331    /// aborting, but this is not a strict requirement. (Specifically: it is *legal* to implement
332    /// this trait atop an underlying native allocation library that aborts on memory exhaustion.)
333    ///
334    /// Clients wishing to abort computation in response to an allocation error are encouraged to
335    /// call the [`handle_alloc_error`] function, rather than directly invoking `panic!` or similar.
336    ///
337    /// [`handle_alloc_error`]: ../../alloc/alloc/fn.handle_alloc_error.html
338    unsafe fn shrink(&self, ptr: NonNull<u8>, old_layout: Layout, new_layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
339        debug_assert!(
340            new_layout.size() <= old_layout.size(),
341            "`new_layout.size()` must be smaller than or equal to `old_layout.size()`"
342        );
343
344        let new_ptr = self.allocate(new_layout)?;
345
346        // SAFETY: because `new_layout.size()` must be lower than or equal to
347        // `old_layout.size()`, both the old and new memory allocation are valid for reads and
348        // writes for `new_layout.size()` bytes. Also, because the old allocation wasn't yet
349        // deallocated, it cannot overlap `new_ptr`. Thus, the call to `copy_nonoverlapping` is
350        // safe. The safety contract for `dealloc` must be upheld by the caller.
351        unsafe {
352            ptr::copy_nonoverlapping(ptr.as_ptr(), non_null::as_mut_ptr(new_ptr), new_layout.size());
353            self.deallocate(ptr, old_layout);
354        }
355
356        Ok(new_ptr)
357    }
358
359    /// Creates a "by reference" adapter for this instance of `Allocator`.
360    ///
361    /// The returned adapter also implements `Allocator` and will simply borrow this.
362    #[inline(always)]
363    fn by_ref(&self) -> &Self
364    where
365        Self: Sized,
366    {
367        self
368    }
369}
370
371unsafe impl<A> Allocator for &A
372where
373    A: Allocator + ?Sized,
374{
375    #[inline]
376    fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
377        (**self).allocate(layout)
378    }
379
380    #[inline]
381    fn allocate_zeroed(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
382        (**self).allocate_zeroed(layout)
383    }
384
385    #[inline]
386    unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
387        // SAFETY: the safety contract must be upheld by the caller
388        unsafe { (**self).deallocate(ptr, layout) }
389    }
390
391    #[inline]
392    unsafe fn grow(&self, ptr: NonNull<u8>, old_layout: Layout, new_layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
393        // SAFETY: the safety contract must be upheld by the caller
394        unsafe { (**self).grow(ptr, old_layout, new_layout) }
395    }
396
397    #[inline]
398    unsafe fn grow_zeroed(
399        &self,
400        ptr: NonNull<u8>,
401        old_layout: Layout,
402        new_layout: Layout,
403    ) -> Result<NonNull<[u8]>, AllocError> {
404        // SAFETY: the safety contract must be upheld by the caller
405        unsafe { (**self).grow_zeroed(ptr, old_layout, new_layout) }
406    }
407
408    #[inline]
409    unsafe fn shrink(&self, ptr: NonNull<u8>, old_layout: Layout, new_layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
410        // SAFETY: the safety contract must be upheld by the caller
411        unsafe { (**self).shrink(ptr, old_layout, new_layout) }
412    }
413}
414
415unsafe impl<A> Allocator for &mut A
416where
417    A: Allocator + ?Sized,
418{
419    #[inline]
420    fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
421        (**self).allocate(layout)
422    }
423
424    #[inline]
425    fn allocate_zeroed(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
426        (**self).allocate_zeroed(layout)
427    }
428
429    #[inline]
430    unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
431        // SAFETY: the safety contract must be upheld by the caller
432        unsafe { (**self).deallocate(ptr, layout) }
433    }
434
435    #[inline]
436    unsafe fn grow(&self, ptr: NonNull<u8>, old_layout: Layout, new_layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
437        // SAFETY: the safety contract must be upheld by the caller
438        unsafe { (**self).grow(ptr, old_layout, new_layout) }
439    }
440
441    #[inline]
442    unsafe fn grow_zeroed(
443        &self,
444        ptr: NonNull<u8>,
445        old_layout: Layout,
446        new_layout: Layout,
447    ) -> Result<NonNull<[u8]>, AllocError> {
448        // SAFETY: the safety contract must be upheld by the caller
449        unsafe { (**self).grow_zeroed(ptr, old_layout, new_layout) }
450    }
451
452    #[inline]
453    unsafe fn shrink(&self, ptr: NonNull<u8>, old_layout: Layout, new_layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
454        // SAFETY: the safety contract must be upheld by the caller
455        unsafe { (**self).shrink(ptr, old_layout, new_layout) }
456    }
457}
458
459// Used for static assertions.
460#[cfg(test)]
461#[derive(Clone)]
462pub(crate) struct NoopAllocator;
463
464#[cfg(test)]
465unsafe impl Allocator for NoopAllocator {
466    fn allocate(&self, _layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
467        Err(AllocError)
468    }
469
470    unsafe fn deallocate(&self, _ptr: NonNull<u8>, _layout: Layout) {}
471}
472
473pub(crate) mod box_like {
474    pub trait Sealed {
475        type T: ?Sized;
476        type A;
477
478        unsafe fn from_raw_in(ptr: *mut Self::T, allocator: Self::A) -> Self;
479    }
480}
481
482/// A type that behaves like [`Box`](alloc_crate::boxed::Box).
483///
484/// This is used for <code>BumpBox::[into_box](crate::BumpBox::into_box)</code>.
485///
486/// **Note:** This trait is also implemented for `allocator_api2` version 0.2's [`Box`](allocator_api2_02::boxed::Box)
487/// but it won't show up in the implementations below because the documentation is built with the `nightly-allocator-api`
488/// feature which makes `allocator_api2` version 0.2's `Box` become nightly's `Box`.
489pub trait BoxLike: box_like::Sealed {}