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