Skip to main content

memapi2/
lib.rs

1//! A small, `no_std`/`no_alloc`-friendly memory allocation interface for managing raw buffers.
2//!
3//! This crate provides explicit layouts, a split allocator trait stack, and structured errors.
4//! It is `no_std` by default but relies on the `alloc` crate unless `no_alloc` is enabled. Enable
5//! `std` for system allocator integrations and `os_err_reporting` for richer diagnostics.
6//!
7//! # Core traits
8//! - [`Alloc`], [`Dealloc`], [`Grow`], [`Shrink`], [`Realloc`]
9//! - Convenience aliases: [`BasicAlloc`] and [`FullAlloc`]
10//! - Optional mutable variants (`alloc_mut_traits`): [`AllocMut`], [`DeallocMut`], [`GrowMut`],
11//!   [`ShrinkMut`], [`ReallocMut`], [`BasicAllocMut`], [`FullAllocMut`]
12//! - Optional scoped allocations (`alloc_temp_trait`): [`AllocTemp`]
13//!
14//! # Types and errors
15//! - [`Layout`]: crate layout type (with conversion to/from [`StdLayout`] unless `no_alloc` is
16//!   enabled)
17//! - [`DefaultAlloc`]: default allocator wrapper that delegates to the global allocator
18//! - Errors: [`Error`](error::Error), [`Cause`](error::Cause), [`LayoutErr`](error::LayoutErr),
19//!   [`ArithErr`](error::ArithErr), [`ArithOp`](error::ArithOp)
20//!
21//! # Data and type utilities
22//! - [`data::type_props`][]: [`SizedProps`](data::type_props::SizedProps),
23//!   [`PtrProps`](data::type_props::PtrProps), [`VarSized`](data::type_props::VarSized),
24//!   [`VarSizedStruct`](data::type_props::VarSizedStruct)
25//! - [`data::marker`]: [`UnsizedCopy`](data::marker::UnsizedCopy), [`Thin`](data::marker::Thin),
26//!   [`SizeMeta`](data::marker::SizeMeta)
27//! - [`helpers`]: alignment, checked arithmetic, and pointer helpers
28//!
29//! # Allocator implementations
30//! - [`DefaultAlloc`] (available unless `no_alloc` is enabled)
31//! - [`std::alloc::System`] when the `std` feature is enabled
32//! - [`c_alloc::CAlloc`] behind the `c_alloc` feature
33//! - [`stack_alloc::StackAlloc`] behind the `stack_alloc` feature
34//!
35//! # Feature flags
36//! - `std`: enables `std` integration (including [`std::alloc::System`])
37//! - `os_err_reporting`: best-effort OS error reporting via `errno` (requires `std`)
38//! - `alloc_mut_traits`: mutable allocator trait variants
39//! - `alloc_temp_trait`: scoped/temporary allocation trait
40//! - `c_alloc`: C `aligned_alloc`-style allocator ([`c_alloc`])
41//! - `stack_alloc`: `alloca`-based allocator ([`stack_alloc`])
42//! - `c_str`: enables `CStr`-specific data traits in `no_std` (MSRV: 1.64)
43//! - `metadata`: enables [`core::ptr::Pointee`] metadata support on nightly
44//! - `sized_hierarchy`: enables [`core::marker::MetaSized`] support on nightly
45//! - `full`, `full_nightly`: convenience bundles for docs/tests
46
47#![allow(unknown_lints)]
48#![warn(clippy::all, clippy::pedantic, clippy::nursery, clippy::multiple_unsafe_ops_per_block)]
49#![allow(
50    clippy::inline_always,
51    clippy::borrow_as_ptr,
52    clippy::module_name_repetitions,
53    clippy::use_self,
54    clippy::question_mark,
55    unused_unsafe
56)]
57#![deny(missing_docs, clippy::undocumented_unsafe_blocks)]
58#![warn(unknown_lints)]
59#![cfg_attr(feature = "dev", warn(rustdoc::broken_intra_doc_links))]
60#![cfg_attr(not(feature = "std"), no_std)]
61// nightly is set by the build.rs
62#![cfg_attr(nightly, feature(allocator_api))]
63#![cfg_attr(feature = "metadata", feature(ptr_metadata))]
64#![cfg_attr(feature = "sized_hierarchy", feature(sized_hierarchy))]
65
66// TODO: add any missing cfg_attr(miri, track_caller) attributes, remove unnecessary ones
67// TODO: consider behavior of all allocation methods in all possible cases for all allocators and
68//  make sure they match and make sense
69
70// TODO: ideally `no_alloc` is ignored/switches to `std` if it's enabled
71
72#[cfg(not(feature = "no_alloc"))] extern crate alloc;
73extern crate core;
74
75/// This macro is theoretically faster than `<fallible>?`.
76macro_rules! tri {
77    (::$err:ident $($fallible:expr)+) => {
78        match $($fallible)+ {
79            Ok(x) => x,
80            Err(e) => return Err(Error::$err(e)),
81        }
82    };
83    (opt $($fallible:expr)+) => {
84        match $($fallible)+ {
85            Some(x) => x,
86            None => return None,
87        }
88    };
89    (do $($fallible:expr)+) => {
90        match $($fallible)+ {
91            Ok(s) => s,
92            Err(e) => return Err(e),
93        }
94    };
95    (cmap($err:expr) from $e:ty, $($fallible:expr)+) => {
96        match $($fallible)+ {
97            Ok(s) => s,
98            Err(_) => return Err(<$e>::from($err)),
99        }
100    };
101}
102
103macro_rules! zalloc {
104    ($self:ident, $alloc:ident, $layout:ident) => {{
105        let res = $self.$alloc($layout);
106        if let Ok(p) = res {
107            // SAFETY: alloc returns at least layout.size() allocated bytes
108            unsafe {
109                ptr::write_bytes(p.as_ptr(), 0, $layout.size());
110            }
111        }
112        res
113    }};
114}
115
116/// The library's main traits.
117pub mod traits;
118pub use traits::*;
119
120/// Helpers that tend to be useful in other libraries as well.
121pub mod helpers;
122
123/// Errors that can occur during allocation.
124pub mod error;
125
126mod layout;
127pub use layout::Layout;
128
129mod ffi;
130
131mod allocs;
132#[allow(unused_imports)] pub use allocs::*;
133
134#[cfg(not(feature = "no_alloc"))]
135/// A type alias for [`alloc::alloc::Layout`].
136pub type StdLayout = alloc::alloc::Layout;
137
138/// Default allocator, delegating to the global allocator.
139///
140/// # Note
141///
142/// This must _not_ be set as the global allocator (via `#[global_allocator]`). Doing so will lead
143/// to infinite recursion, as the allocation functions this calls (in [`alloc::alloc`]) delegate to
144/// the global allocator.
145#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
146pub struct DefaultAlloc;
147
148macro_rules! default_alloc_impl {
149    ($ty:ty) => {
150        #[cfg(not(feature = "no_alloc"))]
151        impl crate::Alloc for $ty {
152            type Error = crate::error::Error;
153
154            #[cfg_attr(miri, track_caller)]
155            #[inline(always)]
156            fn alloc(&self, layout: Layout) -> Result<core::ptr::NonNull<u8>, crate::error::Error> {
157                crate::helpers::null_q_dyn_zsl_check(
158                    layout,
159                    // SAFETY: we check the layout is non-zero-sized before use.
160                    |layout| unsafe { alloc::alloc::alloc(layout.to_stdlib()) }
161                )
162            }
163
164            #[cfg_attr(miri, track_caller)]
165            #[inline(always)]
166            fn zalloc(
167                &self,
168                layout: Layout
169            ) -> Result<core::ptr::NonNull<u8>, crate::error::Error> {
170                crate::helpers::null_q_dyn_zsl_check(
171                    layout,
172                    // SAFETY: we check the layout is non-zero-sized before use.
173                    |layout| unsafe { alloc::alloc::alloc_zeroed(layout.to_stdlib()) }
174                )
175            }
176        }
177        #[cfg(not(feature = "no_alloc"))]
178        impl crate::Dealloc for $ty {
179            #[cfg_attr(miri, track_caller)]
180            #[inline(always)]
181            unsafe fn dealloc(&self, ptr: core::ptr::NonNull<u8>, layout: Layout) {
182                if layout.is_nonzero_sized() && ptr != layout.dangling() {
183                    alloc::alloc::dealloc(ptr.as_ptr(), layout.to_stdlib());
184                }
185            }
186
187            #[cfg_attr(miri, track_caller)]
188            #[inline(always)]
189            unsafe fn try_dealloc(
190                &self,
191                ptr: core::ptr::NonNull<u8>,
192                layout: Layout
193            ) -> Result<(), crate::error::Error> {
194                if layout.is_zero_sized() {
195                    Err(crate::error::Error::ZeroSizedLayout)
196                } else if ptr == layout.dangling() {
197                    Err(crate::error::Error::DanglingDeallocation)
198                } else {
199                    alloc::alloc::dealloc(ptr.as_ptr(), layout.to_stdlib());
200                    Ok(())
201                }
202            }
203        }
204        #[cfg(not(feature = "no_alloc"))]
205        impl crate::Grow for $ty {}
206        #[cfg(not(feature = "no_alloc"))]
207        impl crate::Shrink for $ty {}
208        #[cfg(not(feature = "no_alloc"))]
209        impl crate::Realloc for $ty {}
210    };
211}
212
213#[cfg(not(feature = "no_alloc"))]
214// SAFETY: DefaultAlloc doesn't unwind, and all layout operations are correct
215unsafe impl alloc::alloc::GlobalAlloc for DefaultAlloc {
216    #[cfg_attr(miri, track_caller)]
217    #[inline]
218    unsafe fn alloc(&self, layout: StdLayout) -> *mut u8 {
219        alloc::alloc::alloc(layout)
220    }
221
222    #[cfg_attr(miri, track_caller)]
223    #[inline]
224    unsafe fn dealloc(&self, ptr: *mut u8, layout: StdLayout) {
225        alloc::alloc::dealloc(ptr, layout);
226    }
227
228    #[cfg_attr(miri, track_caller)]
229    #[inline]
230    unsafe fn alloc_zeroed(&self, layout: StdLayout) -> *mut u8 {
231        alloc::alloc::alloc_zeroed(layout)
232    }
233
234    #[cfg_attr(miri, track_caller)]
235    #[inline]
236    unsafe fn realloc(&self, ptr: *mut u8, layout: StdLayout, new_size: usize) -> *mut u8 {
237        alloc::alloc::realloc(ptr, layout, new_size)
238    }
239}
240
241default_alloc_impl!(DefaultAlloc);
242
243#[cfg(all(nightly, not(feature = "no_alloc")))]
244/// The primary module for when `nightly` is enabled.
245pub(crate) mod nightly {
246    use {
247        crate::{Layout, StdLayout},
248        alloc::alloc::{AllocError, Allocator, Global},
249        core::ptr::NonNull
250    };
251
252    // SAFETY: DefaultAlloc's allocated memory isn't deallocated until a deallocation method is
253    //  called. as a ZST allocator, copying/cloning it doesn't change behaviour or invalidate
254    //  allocations.
255    unsafe impl Allocator for crate::DefaultAlloc {
256        #[cfg_attr(miri, track_caller)]
257        #[inline]
258        fn allocate(&self, layout: StdLayout) -> Result<NonNull<[u8]>, AllocError> {
259            Allocator::allocate(&Global, layout)
260        }
261
262        #[cfg_attr(miri, track_caller)]
263        #[inline]
264        fn allocate_zeroed(&self, layout: StdLayout) -> Result<NonNull<[u8]>, AllocError> {
265            Allocator::allocate_zeroed(&Global, layout)
266        }
267
268        #[cfg_attr(miri, track_caller)]
269        #[inline]
270        unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: StdLayout) {
271            Allocator::deallocate(&Global, ptr.cast(), layout);
272        }
273
274        #[cfg_attr(miri, track_caller)]
275        #[inline]
276        unsafe fn grow(
277            &self,
278            ptr: NonNull<u8>,
279            old_layout: StdLayout,
280            new_layout: StdLayout
281        ) -> Result<NonNull<[u8]>, AllocError> {
282            Allocator::grow(&Global, ptr.cast(), old_layout, new_layout)
283        }
284
285        #[cfg_attr(miri, track_caller)]
286        #[inline]
287        unsafe fn grow_zeroed(
288            &self,
289            ptr: NonNull<u8>,
290            old_layout: StdLayout,
291            new_layout: StdLayout
292        ) -> Result<NonNull<[u8]>, AllocError> {
293            Allocator::grow_zeroed(&Global, ptr.cast(), old_layout, new_layout)
294        }
295
296        #[cfg_attr(miri, track_caller)]
297        #[inline]
298        unsafe fn shrink(
299            &self,
300            ptr: NonNull<u8>,
301            old_layout: StdLayout,
302            new_layout: StdLayout
303        ) -> Result<NonNull<[u8]>, AllocError> {
304            Allocator::shrink(&Global, ptr.cast(), old_layout, new_layout)
305        }
306    }
307
308    default_alloc_impl!(Global);
309
310    // TODO: either Allocator for A: Alloc or vice versa, not sure which. i think i removed that at
311    // some point but i can't remember why.
312}