zallocators/
lib.rs

1use core::alloc::Layout;
2use core::fmt;
3use core::ptr::{self, NonNull};
4use std::error::Error;
5
6mod fixed_buf;
7pub use fixed_buf::FixedBufferAllocator;
8
9mod vec;
10pub use vec::Vec;
11
12#[derive(Debug)]
13pub struct AllocError;
14
15impl Error for AllocError {}
16
17impl fmt::Display for AllocError {
18    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
19        f.write_str("memory allocation failed")
20    }
21}
22
23pub unsafe trait Allocator {
24    /// Attempts to allocate a block of memory.
25    ///
26    /// On success, returns a [`NonNull<[u8]>`][NonNull] meeting the size and alignment guarantees of `layout`.
27    ///
28    /// The returned block may have a larger size than specified by `layout.size()`, and may or may
29    /// not have its contents initialized.
30    ///
31    /// # Errors
32    ///
33    /// Returning `Err` indicates that either memory is exhausted or `layout` does not meet
34    /// allocator's size or alignment constraints.
35    ///
36    /// Implementations are encouraged to return `Err` on memory exhaustion rather than panicking or
37    /// aborting, but this is not a strict requirement. (Specifically: it is *legal* to implement
38    /// this trait atop an underlying native allocation library that aborts on memory exhaustion.)
39    ///
40    /// Clients wishing to abort computation in response to an allocation error are encouraged to
41    /// call the [`handle_alloc_error`] function, rather than directly invoking `panic!` or similar.
42    ///
43    /// [`handle_alloc_error`]: ../../alloc/alloc/fn.handle_alloc_error.html
44    fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError>;
45
46    /// Behaves like `allocate`, but also ensures that the returned memory is zero-initialized.
47    ///
48    /// # Errors
49    ///
50    /// Returning `Err` indicates that either memory is exhausted or `layout` does not meet
51    /// allocator's size or alignment constraints.
52    ///
53    /// Implementations are encouraged to return `Err` on memory exhaustion rather than panicking or
54    /// aborting, but this is not a strict requirement. (Specifically: it is *legal* to implement
55    /// this trait atop an underlying native allocation library that aborts on memory exhaustion.)
56    ///
57    /// Clients wishing to abort computation in response to an allocation error are encouraged to
58    /// call the [`handle_alloc_error`] function, rather than directly invoking `panic!` or similar.
59    ///
60    /// [`handle_alloc_error`]: ../../alloc/alloc/fn.handle_alloc_error.html
61    fn allocate_zeroed(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
62        let ptr = self.allocate(layout)?;
63        // SAFETY: `alloc` returns a valid memory block
64        let raw_ptr = ptr.as_ptr() as *mut u8;
65        unsafe { raw_ptr.write_bytes(0, ptr.len()) };
66        Ok(ptr)
67    }
68
69    /// Deallocates the memory referenced by `ptr`.
70    ///
71    /// # Safety
72    ///
73    /// * `ptr` must denote a block of memory [*currently allocated*] via this allocator, and
74    /// * `layout` must [*fit*] that block of memory.
75    ///
76    /// [*currently allocated*]: #currently-allocated-memory
77    /// [*fit*]: #memory-fitting
78    unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout);
79
80    /// Attempts to extend the memory block.
81    ///
82    /// Returns a new [`NonNull<[u8]>`][NonNull] containing a pointer and the actual size of the allocated
83    /// memory. The pointer is suitable for holding data described by `new_layout`. To accomplish
84    /// this, the allocator may extend the allocation referenced by `ptr` to fit the new layout.
85    ///
86    /// If this returns `Ok`, then ownership of the memory block referenced by `ptr` has been
87    /// transferred to this allocator. Any access to the old `ptr` is Undefined Behavior, even if the
88    /// allocation was grown in-place. The newly returned pointer is the only valid pointer
89    /// for accessing this memory now.
90    ///
91    /// If this method returns `Err`, then ownership of the memory block has not been transferred to
92    /// this allocator, and the contents of the memory block are unaltered.
93    ///
94    /// # Safety
95    ///
96    /// * `ptr` must denote a block of memory [*currently allocated*] via this allocator.
97    /// * `old_layout` must [*fit*] that block of memory (The `new_layout` argument need not fit it.).
98    /// * `new_layout.size()` must be greater than or equal to `old_layout.size()`.
99    ///
100    /// Note that `new_layout.align()` need not be the same as `old_layout.align()`.
101    ///
102    /// [*currently allocated*]: #currently-allocated-memory
103    /// [*fit*]: #memory-fitting
104    ///
105    /// # Errors
106    ///
107    /// Returns `Err` if the new layout does not meet the allocator's size and alignment
108    /// constraints of the allocator, or if growing otherwise fails.
109    ///
110    /// Implementations are encouraged to return `Err` on memory exhaustion rather than panicking or
111    /// aborting, but this is not a strict requirement. (Specifically: it is *legal* to implement
112    /// this trait atop an underlying native allocation library that aborts on memory exhaustion.)
113    ///
114    /// Clients wishing to abort computation in response to an allocation error are encouraged to
115    /// call the [`handle_alloc_error`] function, rather than directly invoking `panic!` or similar.
116    ///
117    /// [`handle_alloc_error`]: ../../alloc/alloc/fn.handle_alloc_error.html
118    unsafe fn grow(
119        &self,
120        ptr: NonNull<u8>,
121        old_layout: Layout,
122        new_layout: Layout,
123    ) -> Result<NonNull<[u8]>, AllocError> {
124        debug_assert!(
125            new_layout.size() >= old_layout.size(),
126            "`new_layout.size()` must be greater than or equal to `old_layout.size()`"
127        );
128
129        let new_ptr = self.allocate(new_layout)?;
130        let new_raw_ptr = new_ptr.as_ptr() as *mut u8;
131
132        // SAFETY: because `new_layout.size()` must be greater than or equal to
133        // `old_layout.size()`, both the old and new memory allocation are valid for reads and
134        // writes for `old_layout.size()` bytes. Also, because the old allocation wasn't yet
135        // deallocated, it cannot overlap `new_ptr`. Thus, the call to `copy_nonoverlapping` is
136        // safe. The safety contract for `dealloc` must be upheld by the caller.
137        unsafe {
138            ptr::copy_nonoverlapping(ptr.as_ptr(), new_raw_ptr, old_layout.size());
139            self.deallocate(ptr, old_layout);
140        }
141
142        Ok(new_ptr)
143    }
144
145    /// Behaves like `grow`, but also ensures that the new contents are set to zero before being
146    /// returned.
147    ///
148    /// The memory block will contain the following contents after a successful call to
149    /// `grow_zeroed`:
150    ///   * Bytes `0..old_layout.size()` are preserved from the original allocation.
151    ///   * Bytes `old_layout.size()..old_size` will either be preserved or zeroed, depending on
152    ///     the allocator implementation. `old_size` refers to the size of the memory block prior
153    ///     to the `grow_zeroed` call, which may be larger than the size that was originally
154    ///     requested when it was allocated.
155    ///   * Bytes `old_size..new_size` are zeroed. `new_size` refers to the size of the memory
156    ///     block returned by the `grow_zeroed` call.
157    ///
158    /// # Safety
159    ///
160    /// * `ptr` must denote a block of memory [*currently allocated*] via this allocator.
161    /// * `old_layout` must [*fit*] that block of memory (The `new_layout` argument need not fit it.).
162    /// * `new_layout.size()` must be greater than or equal to `old_layout.size()`.
163    ///
164    /// Note that `new_layout.align()` need not be the same as `old_layout.align()`.
165    ///
166    /// [*currently allocated*]: #currently-allocated-memory
167    /// [*fit*]: #memory-fitting
168    ///
169    /// # Errors
170    ///
171    /// Returns `Err` if the new layout does not meet the allocator's size and alignment
172    /// constraints of the allocator, or if growing otherwise fails.
173    ///
174    /// Implementations are encouraged to return `Err` on memory exhaustion rather than panicking or
175    /// aborting, but this is not a strict requirement. (Specifically: it is *legal* to implement
176    /// this trait atop an underlying native allocation library that aborts on memory exhaustion.)
177    ///
178    /// Clients wishing to abort computation in response to an allocation error are encouraged to
179    /// call the [`handle_alloc_error`] function, rather than directly invoking `panic!` or similar.
180    ///
181    /// [`handle_alloc_error`]: ../../alloc/alloc/fn.handle_alloc_error.html
182    unsafe fn grow_zeroed(
183        &self,
184        ptr: NonNull<u8>,
185        old_layout: Layout,
186        new_layout: Layout,
187    ) -> Result<NonNull<[u8]>, AllocError> {
188        debug_assert!(
189            new_layout.size() >= old_layout.size(),
190            "`new_layout.size()` must be greater than or equal to `old_layout.size()`"
191        );
192
193        let new_ptr = self.allocate_zeroed(new_layout)?;
194        let new_raw_ptr = new_ptr.as_ptr() as *mut u8;
195        // SAFETY: because `new_layout.size()` must be greater than or equal to
196        // `old_layout.size()`, both the old and new memory allocation are valid for reads and
197        // writes for `old_layout.size()` bytes. Also, because the old allocation wasn't yet
198        // deallocated, it cannot overlap `new_ptr`. Thus, the call to `copy_nonoverlapping` is
199        // safe. The safety contract for `dealloc` must be upheld by the caller.
200        unsafe {
201            ptr::copy_nonoverlapping(ptr.as_ptr(), new_raw_ptr, old_layout.size());
202            self.deallocate(ptr, old_layout);
203        }
204
205        Ok(new_ptr)
206    }
207
208    /// Attempts to shrink the memory block.
209    ///
210    /// Returns a new [`NonNull<[u8]>`][NonNull] containing a pointer and the actual size of the allocated
211    /// memory. The pointer is suitable for holding data described by `new_layout`. To accomplish
212    /// this, the allocator may shrink the allocation referenced by `ptr` to fit the new layout.
213    ///
214    /// If this returns `Ok`, then ownership of the memory block referenced by `ptr` has been
215    /// transferred to this allocator. Any access to the old `ptr` is Undefined Behavior, even if the
216    /// allocation was shrunk in-place. The newly returned pointer is the only valid pointer
217    /// for accessing this memory now.
218    ///
219    /// If this method returns `Err`, then ownership of the memory block has not been transferred to
220    /// this allocator, and the contents of the memory block are unaltered.
221    ///
222    /// # Safety
223    ///
224    /// * `ptr` must denote a block of memory [*currently allocated*] via this allocator.
225    /// * `old_layout` must [*fit*] that block of memory (The `new_layout` argument need not fit it.).
226    /// * `new_layout.size()` must be smaller than or equal to `old_layout.size()`.
227    ///
228    /// Note that `new_layout.align()` need not be the same as `old_layout.align()`.
229    ///
230    /// [*currently allocated*]: #currently-allocated-memory
231    /// [*fit*]: #memory-fitting
232    ///
233    /// # Errors
234    ///
235    /// Returns `Err` if the new layout does not meet the allocator's size and alignment
236    /// constraints of the allocator, or if shrinking otherwise fails.
237    ///
238    /// Implementations are encouraged to return `Err` on memory exhaustion rather than panicking or
239    /// aborting, but this is not a strict requirement. (Specifically: it is *legal* to implement
240    /// this trait atop an underlying native allocation library that aborts on memory exhaustion.)
241    ///
242    /// Clients wishing to abort computation in response to an allocation error are encouraged to
243    /// call the [`handle_alloc_error`] function, rather than directly invoking `panic!` or similar.
244    ///
245    /// [`handle_alloc_error`]: ../../alloc/alloc/fn.handle_alloc_error.html
246    unsafe fn shrink(
247        &self,
248        ptr: NonNull<u8>,
249        old_layout: Layout,
250        new_layout: Layout,
251    ) -> Result<NonNull<[u8]>, AllocError> {
252        debug_assert!(
253            new_layout.size() <= old_layout.size(),
254            "`new_layout.size()` must be smaller than or equal to `old_layout.size()`"
255        );
256
257        let new_ptr = self.allocate(new_layout)?;
258        let new_raw_ptr = new_ptr.as_ptr() as *mut u8;
259        // SAFETY: because `new_layout.size()` must be lower than or equal to
260        // `old_layout.size()`, both the old and new memory allocation are valid for reads and
261        // writes for `new_layout.size()` bytes. Also, because the old allocation wasn't yet
262        // deallocated, it cannot overlap `new_ptr`. Thus, the call to `copy_nonoverlapping` is
263        // safe. The safety contract for `dealloc` must be upheld by the caller.
264        unsafe {
265            ptr::copy_nonoverlapping(ptr.as_ptr(), new_raw_ptr, new_layout.size());
266            self.deallocate(ptr, old_layout);
267        }
268
269        Ok(new_ptr)
270    }
271
272    /// Creates a "by reference" adapter for this instance of `Allocator`.
273    ///
274    /// The returned adapter also implements `Allocator` and will simply borrow this.
275    #[inline(always)]
276    fn by_ref(&self) -> &Self
277    where
278        Self: Sized,
279    {
280        self
281    }
282}