Skip to main content

zigzag_alloc/alloc/
allocator.rs

1//! Core allocator abstraction.
2//!
3//! This module defines the [`Allocator`] trait — the single interface that
4//! every allocator in the crate implements and that every collection depends on.
5//!
6//! The design is deliberately minimal: two methods (`alloc` / `dealloc`) that
7//! mirror the semantics of `posix_memalign` / `free` while being safe to
8//! compose behind higher-level wrappers.
9
10use core::alloc::Layout;
11use core::ptr::NonNull;
12
13/// Low-level memory allocator interface.
14///
15/// Inspired by the explicit allocation model of Zig, this trait decouples
16/// collections from any concrete or global allocator.  Callers retain full
17/// control over *which* memory region is used and *when* it is released.
18///
19/// # Implementing `Allocator`
20///
21/// An implementation must uphold the following invariants:
22///
23/// * A successful call to [`alloc`](Allocator::alloc) returns a pointer that is
24///   valid for `layout.size()` bytes and aligned to at least `layout.align()`.
25/// * The returned memory may contain arbitrary (uninitialized) bytes.
26/// * The allocated block remains valid until an explicit call to
27///   [`dealloc`](Allocator::dealloc) with the *exact same pointer and layout*.
28/// * Calling `dealloc` with a pointer that was not obtained from the same
29///   allocator instance, or with a mismatched layout, is undefined behaviour.
30pub trait Allocator {
31    /// Allocates a block of memory described by `layout`.
32    ///
33    /// Returns `Some(ptr)` on success, where `ptr` is non-null and correctly
34    /// aligned.  Returns `None` when the allocator cannot satisfy the request
35    /// (e.g. out of memory, capacity exhausted).
36    ///
37    /// # Safety
38    ///
39    /// * `layout.size()` **must** be greater than zero.  Passing a zero-sized
40    ///   layout is undefined behaviour; implementations may panic or return a
41    ///   dangling pointer, but callers must never rely on that behaviour.
42    /// * The caller must eventually call [`dealloc`](Allocator::dealloc) with
43    ///   the returned pointer and the *same* `layout`, unless the allocator
44    ///   uses bulk reclamation (e.g. arena reset).
45    unsafe fn alloc(&self, layout: Layout) -> Option<NonNull<u8>>;
46
47    /// Releases the memory block previously obtained from this allocator.
48    ///
49    /// For allocators that use bulk reclamation (e.g. [`ArenaAllocator`],
50    /// [`BumpAllocator`]) this method is intentionally a no-op; memory is
51    /// reclaimed collectively via `reset()` or on drop.
52    ///
53    /// # Safety
54    ///
55    /// * `ptr` **must** have been returned by a prior call to
56    ///   [`alloc`](Allocator::alloc) on the **same** allocator instance.
57    /// * `layout` **must** exactly match the layout passed to that `alloc` call.
58    /// * After `dealloc` returns, `ptr` is invalid and must never be
59    ///   dereferenced (prevents use-after-free).
60    /// * Calling `dealloc` twice with the same pointer is undefined behaviour
61    ///   (double-free).
62    ///
63    /// [`ArenaAllocator`]: crate::alloc::arena::ArenaAllocator
64    /// [`BumpAllocator`]: crate::alloc::bump::BumpAllocator
65    unsafe fn dealloc(&self, ptr: NonNull<u8>, layout: Layout);
66}
67
68/// Blanket implementation of [`Allocator`] for shared references.
69///
70/// This allows passing `&A` (or `&dyn Allocator`) into collections that are
71/// generic over `A: Allocator`, avoiding the need to move ownership of the
72/// allocator into every container.
73///
74/// # Example
75///
76/// ```rust,ignore
77/// let sys = SystemAllocator;
78/// let vec: ExVec<u32> = ExVec::new(&sys); // &SystemAllocator implements Allocator
79/// ```
80impl<A: Allocator + ?Sized> Allocator for &A {
81    /// Forwards the allocation request to the underlying allocator.
82    ///
83    /// # Safety
84    ///
85    /// Inherits all safety requirements from [`A::alloc`](Allocator::alloc).
86    #[inline]
87    unsafe fn alloc(&self, layout: Layout) -> Option<NonNull<u8>> {
88        // SAFETY: The caller is responsible for satisfying the layout preconditions
89        // (non-zero size, valid alignment).  We simply forward the call unchanged.
90        unsafe { (**self).alloc(layout) }
91    }
92
93    /// Forwards the deallocation request to the underlying allocator.
94    ///
95    /// # Safety
96    ///
97    /// Inherits all safety requirements from [`A::dealloc`](Allocator::dealloc).
98    /// In particular, `ptr` must have been allocated by the same underlying
99    /// allocator instance and `layout` must match the original allocation.
100    #[inline]
101    unsafe fn dealloc(&self, ptr: NonNull<u8>, layout: Layout) {
102        // SAFETY: The caller guarantees `ptr` and `layout` are valid for the
103        // underlying allocator.  We forward without modification.
104        unsafe { (**self).dealloc(ptr, layout) }
105    }
106}