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 {}