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}