memapi2/
lib.rs

1//! A small, `no_std`-friendly memory allocation interface for managing raw
2//! buffers, suitable for use in collections.
3//!
4//! This crate focuses on a minimal API:
5//! - [`Alloc`]: a trait defining basic allocation, zero-allocation, deallocation, and simple
6//!   grow/shrink helpers.
7//! - [`DefaultAlloc`]: a tiny wrapper delegating to the global allocator.
8//! - [`AllocError`]: an error type describing allocation failures.
9//!
10//! Some utilities and marker traits are provided under [`data`]:
11//! - [`PtrProps`](data::type_props::PtrProps)
12//! - [`SizedProps`](data::type_props::SizedProps)
13//! - [`VarSized`](data::type_props::VarSized)
14//! - [`UnsizedCopy`](data::marker::UnsizedCopy)
15//! - [`Thin`](data::marker::Thin)
16
17// TODO: add more tests and benches
18// TODO: test on other platforms/targets
19
20#![allow(unknown_lints)]
21#![warn(
22    clippy::all,
23    clippy::pedantic,
24    clippy::nursery,
25    clippy::borrow_as_ptr,
26    clippy::undocumented_unsafe_blocks
27)]
28#![warn(unknown_lints)]
29#![allow(
30    unsafe_op_in_unsafe_fn,
31    rustdoc::broken_intra_doc_links,
32    // does anyone else hate the Self keyword? that capital letter there looks so ugly idk why
33    clippy::use_self
34)]
35#![deny(missing_docs, unused_unsafe)]
36#![cfg_attr(not(feature = "std"), no_std)]
37#![cfg_attr(feature = "nightly", feature(allocator_api))]
38#![cfg_attr(feature = "metadata", feature(ptr_metadata))]
39#![cfg_attr(feature = "clone_to_uninit", feature(clone_to_uninit))]
40#![cfg_attr(feature = "sized_hierarchy", feature(sized_hierarchy))]
41
42// TODO: add any missing cfg_attr(miri, track_caller) attributes, remove unnecessary ones
43// TODO: a lot of helpers and unstable utils would be good to have in another crate, maybe?
44
45macro_rules! const_if {
46    (
47        $feature:literal,
48        $docs:literal,
49        $(#[$attr:meta])*
50        // this is also pretty poorly done, but it makes type param and optional req work
51        pub const fn $name:ident $(<$generic_ty:ident $(: $req:ident)?>)? ( $($args:tt)* )
52        $(-> $ret:ty)?
53        // also kinda poorly done, but it makes a single where clause work
54        $(where $where_ty:ident : $where_req:ident)?
55        $body:block
56    ) => {
57        // when the feature is enabled, emit a `const fn`
58        #[cfg(feature = $feature)]
59        #[doc = $docs]
60        // feature should only be enabled on compatible versions, so we allow this
61        #[allow(clippy::incompatible_msrv)]
62        $(#[$attr])*
63        pub const fn $name $(<$generic_ty $(: $req)?>)? ($($args)*)
64        $(-> $ret)? $(where $where_ty: $where_req)? $body
65
66        // when the feature is disabled, drop the `const`
67        #[cfg(not(feature = $feature))]
68        #[doc = $docs]
69        $(#[$attr])*
70        #[allow(unknown_lints, clippy::missing_const_for_fn)]
71        pub fn $name $(<$generic_ty $(: $req)?>)? ($($args)*)
72        $(-> $ret)? $(where $where_ty: $where_req)? $body
73    };
74    // branch for a lifetime instead of type param
75    (
76        $feature:literal,
77        $docs:literal,
78        $(#[$attr:meta])*
79        // this is also pretty poorly done, but it makes type param and optional req work
80        pub const fn $name:ident $(<$lt:lifetime>)? ( $($args:tt)* )
81        $(-> $ret:ty)?
82        $body:block
83    ) => {
84        #[cfg(feature = $feature)]
85        #[doc = $docs]
86        #[allow(clippy::incompatible_msrv)]
87        $(#[$attr])*
88        pub const fn $name $(<$lt>)? ($($args)*)
89        $(-> $ret)? $body
90
91        #[cfg(not(feature = $feature))]
92        #[doc = $docs]
93        $(#[$attr])*
94        pub fn $name $(<$lt>)? ($($args)*) $(-> $ret)? $body
95    };
96    // branch for unsafe functions
97    (
98        $feature:literal,
99        $docs:literal,
100        $(#[$attr:meta])*
101        //                                kinda poorly done, but it makes a type param work, which
102        //                                is all i need.
103        pub const unsafe fn $name:ident $(<$generic_ty:ident $(: $req:ident)?>)? ( $($args:tt)* )
104        $(-> $ret:ty)?
105        $body:block
106    ) => {
107        #[cfg(feature = $feature)]
108        #[doc = $docs]
109        #[allow(clippy::incompatible_msrv)]
110        $(#[$attr])*
111        pub const unsafe fn $name$(<$generic_ty $(: $req)?>)?($($args)*) $(-> $ret)? $body
112
113        #[cfg(not(feature = $feature))]
114        #[doc = $docs]
115        $(#[$attr])*
116        pub unsafe fn $name$(<$generic_ty $(: $req)?>)?($($args)*) $(-> $ret)? $body
117    };
118    // branch for relaxed bound + second bound
119    (
120        $feature:literal,
121        $docs:literal,
122        $(#[$attr:meta])*
123        pub const fn $name:ident <$generic_ty:ident: ?Sized + $req:ident> ($($args:tt)*)
124        $(-> $ret:ty)?
125        $body:block
126    ) => {
127        #[cfg(feature = $feature)]
128        #[doc = $docs]
129        #[allow(clippy::incompatible_msrv)]
130        $(#[$attr])*
131        pub const fn $name <$generic_ty: ?Sized + $req> ($($args)*)
132        $(-> $ret)? $body
133
134        #[cfg(not(feature = $feature))]
135        #[doc = $docs]
136        $(#[$attr])*
137        pub fn $name <$generic_ty: ?Sized + $req> ($($args)*)
138        $(-> $ret)? $body
139    }
140}
141
142/// This macro is theoretically faster than `<fallible>?`.
143macro_rules! tri {
144    (do $($fallible:expr)+) => {
145        match $($fallible)+ {
146            Ok(s) => s,
147            Err(e) => return Err(e),
148        }
149    };
150    (AllocError::$n:ident($($fallible:expr)+)) => {
151        match $($fallible)+ {
152            Ok(s) => s,
153            Err(e) => return Err(crate::error::AllocError::$n(e)),
154        }
155    };
156    (lay, $sz:expr, $aln:expr) => {
157        match crate::unstable_util::lay_from_size_align($sz, $aln) {
158            Ok(layout) => layout,
159            Err(e) => return Err(
160                crate::error::AllocError::InvalidLayout(crate::error::InvLayout($sz, $aln, e))
161            ),
162        }
163    };
164    (lay_preproc $layout:ident) => {{
165        let pre = preproc_layout($layout);
166        match pre {
167            Ok(layout) => layout,
168            Err(e) => return Err(crate::error::AllocError::InvalidLayout(e)),
169        }
170    }}
171}
172
173#[allow(unused_macros)]
174macro_rules! assume {
175    ($e:expr) => {
176        #[cfg(feature = "assumptions")]
177        {
178            let res = $e;
179
180            #[cfg(debug_assertions)]
181            {
182                assert!(res, concat!("assertion failed: ", stringify!($e)));
183            }
184            crate::assert_unreachable(res);
185        }
186    };
187    (u_pre $e:expr, $msg:literal) => {
188        #[cfg(feature = "assumptions")]
189        {
190            let res = $e;
191            #[cfg(debug_assertions)]
192            {
193                assert!(
194                    res,
195                    concat!("unsafe precondition `", stringify!($e), "` violated: ", $msg)
196                );
197            }
198            crate::assert_unreachable(res);
199        }
200    };
201}
202
203/// Asserts a boolean value to be true, and the false condition to be unreachable.
204///
205/// # Safety
206///
207/// This is only safe to call if `cond` is `true`. See
208/// [`unreachable_unchecked`](core::hint::unreachable_unchecked) for more details.
209#[cfg_attr(not(feature = "dev"), doc(hidden))]
210pub const unsafe fn assert_unreachable(cond: bool) {
211    if !cond {
212        #[allow(clippy::incompatible_msrv)]
213        core::hint::unreachable_unchecked();
214    }
215}
216
217// TODO: split crate into smaller crates (memapi-jemalloc, memapi-mimalloc, etc.)
218//  (removed stuff is stuff which would go in new crate)
219
220extern crate alloc;
221extern crate core;
222
223/// Module for anything related specifically to data.
224///
225/// This includes marker traits, type properties, and miscellaneous data-handling traits.
226pub mod data;
227
228/// Small alternatives to Rust functions that are unstable as of the most recent release.
229pub mod unstable_util;
230
231/// Errors that can occur during allocation.
232pub mod error;
233
234use {
235    core::{
236        alloc::Layout,
237        cmp::Ordering,
238        ptr::{self, NonNull}
239    },
240    error::AllocError,
241    helpers::alloc_then
242};
243
244/// Default allocator, delegating to the global allocator.
245#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
246pub struct DefaultAlloc;
247
248#[allow(unused_macros)]
249macro_rules! default_alloc_impl {
250    ($ty:ty) => {
251        impl crate::Alloc for $ty {
252            #[cfg_attr(miri, track_caller)]
253            #[inline(always)]
254            fn alloc(
255                &self,
256                layout: Layout
257            ) -> Result<core::ptr::NonNull<u8>, crate::error::AllocError> {
258                crate::helpers::null_q_zsl_check(
259                    layout,
260                    // SAFETY: we check the layout is non zero-sized before use.
261                    |layout| unsafe { alloc::alloc::alloc(layout) },
262                    crate::helpers::null_q_dyn
263                )
264            }
265
266            #[cfg_attr(miri, track_caller)]
267            #[inline(always)]
268            fn zalloc(
269                &self,
270                layout: Layout
271            ) -> Result<core::ptr::NonNull<u8>, crate::error::AllocError> {
272                crate::helpers::null_q_zsl_check(
273                    layout,
274                    // SAFETY: we check the layout is non zero-sized before use.
275                    |layout| unsafe { alloc::alloc::alloc_zeroed(layout) },
276                    crate::helpers::null_q_dyn
277                )
278            }
279
280            #[cfg_attr(miri, track_caller)]
281            #[inline(always)]
282            unsafe fn dealloc(&self, ptr: core::ptr::NonNull<u8>, layout: Layout) {
283                if layout.size() != 0 {
284                    alloc::alloc::dealloc(ptr.as_ptr(), layout);
285                }
286            }
287        }
288    };
289}
290
291// SAFETY: DefaultAlloc doesn't unwind, and all layout operations are correct
292unsafe impl alloc::alloc::GlobalAlloc for DefaultAlloc {
293    #[cfg_attr(miri, track_caller)]
294    #[inline]
295    unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
296        alloc::alloc::alloc(layout)
297    }
298
299    #[cfg_attr(miri, track_caller)]
300    #[inline]
301    unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
302        alloc::alloc::dealloc(ptr, layout);
303    }
304
305    #[cfg_attr(miri, track_caller)]
306    #[inline]
307    unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
308        alloc::alloc::alloc_zeroed(layout)
309    }
310
311    #[cfg_attr(miri, track_caller)]
312    #[inline]
313    unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
314        alloc::alloc::realloc(ptr, layout, new_size)
315    }
316}
317
318default_alloc_impl!(DefaultAlloc);
319
320// MAYBEDO: split this trait into multiple:
321//  - Alloc
322//  - Dealloc
323//  - Grow: Alloc + Dealloc
324//  - Shrink: Alloc + Dealloc
325//  - Realloc: Grow + Shrink
326//  and a supertrait:
327//  - BasicAlloc: Alloc + Dealloc
328/// A memory allocation interface.
329///
330/// This trait does _not_ require `Self: Allocator` and is `no_std`-compatible.
331pub trait Alloc {
332    /// Attempts to allocate a block of memory fitting the given [`Layout`].
333    ///
334    /// # Errors
335    ///
336    /// - [`AllocError::AllocFailed`] if allocation fails.
337    /// - [`AllocError::ZeroSizedLayout`] if `layout` has a size of zero.
338    fn alloc(&self, layout: Layout) -> Result<NonNull<u8>, AllocError>;
339
340    /// Attempts to allocate a zeroed block of memory fitting the given [`Layout`].
341    ///
342    /// # Errors
343    ///
344    /// - [`AllocError::AllocFailed`] if allocation fails.
345    /// - [`AllocError::ZeroSizedLayout`] if `layout` has a size of zero.
346    #[cfg_attr(miri, track_caller)]
347    #[inline]
348    fn zalloc(&self, layout: Layout) -> Result<NonNull<u8>, AllocError> {
349        // SAFETY: alloc returns at least layout.size() allocated bytes
350        unsafe {
351            alloc_then(self, layout, (), |p, ()| {
352                ptr::write_bytes(p.as_ptr(), 0, layout.size());
353                p
354            })
355        }
356    }
357
358    /// Deallocates a previously allocated block.
359    ///
360    /// This is a noop if `layout.size() == 0`.
361    ///
362    /// # Safety
363    ///
364    /// - `ptr` must point to a block of memory allocated using this allocator.
365    /// - `layout` must describe exactly the same block.
366    ///
367    /// # Panics
368    ///
369    /// Some implementations may choose to panic if `ptr` or `layout` are invalid.
370    unsafe fn dealloc(&self, ptr: NonNull<u8>, layout: Layout);
371
372    /// Grow the given block to a new, larger layout.
373    ///
374    /// Returns the new pointer, possibly reallocated elsewhere.
375    ///
376    /// # Safety
377    ///
378    /// - `ptr` must point to a block of memory allocated using this allocator.
379    /// - `old_layout` must describe exactly the same block.
380    ///
381    /// # Errors
382    /// - [`AllocError::AllocFailed`] if allocation fails.
383    /// - [`AllocError::GrowSmallerNewLayout`] if `new_layout.size() < old_layout.size()`.
384    /// - [`AllocError::ZeroSizedLayout`] if `new_layout` has a size of zero.
385    ///
386    /// On failure, the original memory won't be deallocated or modified.
387    #[cfg_attr(miri, track_caller)]
388    #[inline]
389    unsafe fn grow(
390        &self,
391        ptr: NonNull<u8>,
392        old_layout: Layout,
393        new_layout: Layout
394    ) -> Result<NonNull<u8>, AllocError> {
395        grow(self, ptr, old_layout, new_layout, AllocPattern::Uninitialized)
396    }
397
398    /// Grows the given block to a new, larger layout, zeroing any newly allocated bytes.
399    ///
400    /// Returns the new pointer, possibly reallocated elsewhere.
401    ///
402    /// # Safety
403    ///
404    /// - `ptr` must point to a block of memory allocated using this allocator.
405    /// - `old_layout` must describe exactly the same block.
406    ///
407    /// # Errors
408    /// - [`AllocError::AllocFailed`] if allocation fails.
409    /// - [`AllocError::GrowSmallerNewLayout`] in `new_layout.size() < old_layout.size()`.
410    /// - [`AllocError::ZeroSizedLayout`] if `new_layout` has a size of zero.
411    ///
412    /// On failure, the original memory won't be deallocated or modified.
413    #[cfg_attr(miri, track_caller)]
414    unsafe fn zgrow(
415        &self,
416        ptr: NonNull<u8>,
417        old_layout: Layout,
418        new_layout: Layout
419    ) -> Result<NonNull<u8>, AllocError> {
420        grow(self, ptr, old_layout, new_layout, AllocPattern::Zeroed)
421    }
422
423    /// Shrink the given block to a new, smaller layout.
424    ///
425    /// # Safety
426    ///
427    /// - `ptr` must point to a block of memory allocated using this allocator.
428    /// - `old_layout` must describe exactly the same block.
429    ///
430    /// # Errors
431    ///
432    /// - [`AllocError::AllocFailed`] if allocation fails.
433    /// - [`AllocError::ShrinkLargerNewLayout`] if `new_layout.size() > old_layout.size()`.
434    /// - [`AllocError::ZeroSizedLayout`] if `new_layout` has a size of zero.
435    ///
436    /// On failure, the original memory won't be deallocated or modified.
437    #[cfg_attr(miri, track_caller)]
438    unsafe fn shrink(
439        &self,
440        ptr: NonNull<u8>,
441        old_layout: Layout,
442        new_layout: Layout
443    ) -> Result<NonNull<u8>, AllocError> {
444        shrink(self, ptr, old_layout, new_layout)
445    }
446
447    /// Reallocates a block, growing or shrinking as needed.
448    ///
449    ///
450    /// On grow, preserves existing contents up to `old_layout.size()`, and
451    /// on shrink, truncates to `new_layout.size()`.
452    ///
453    /// # Safety
454    ///
455    /// - `ptr` must point to a block previously allocated with this allocator.
456    /// - `old_layout` must describe exactly that block.
457    ///
458    /// # Errors
459    ///
460    /// - [`AllocError::AllocFailed`] if allocation fails.
461    /// - [`AllocError::ZeroSizedLayout`] if `new_layout` has a size of zero.
462    ///
463    /// On failure, the original memory won't be deallocated or modified.
464    #[cfg_attr(miri, track_caller)]
465    unsafe fn realloc(
466        &self,
467        ptr: NonNull<u8>,
468        old_layout: Layout,
469        new_layout: Layout
470    ) -> Result<NonNull<u8>, AllocError> {
471        ralloc(self, ptr, old_layout, new_layout, AllocPattern::Uninitialized)
472    }
473
474    /// Reallocates a block, growing or shrinking as needed, zeroing any newly
475    /// allocated bytes.
476    ///
477    /// # Safety
478    ///
479    /// - `ptr` must point to a block previously allocated with this allocator.
480    /// - `old_layout` must describe exactly that block.
481    ///
482    /// # Errors
483    ///
484    /// - [`AllocError::AllocFailed`] if allocation fails.
485    /// - [`AllocError::ZeroSizedLayout`] if `new_layout` has a size of zero.
486    ///
487    /// On failure, the original memory won't be deallocated or modified.
488    #[cfg_attr(miri, track_caller)]
489    unsafe fn rezalloc(
490        &self,
491        ptr: NonNull<u8>,
492        old_layout: Layout,
493        new_layout: Layout
494    ) -> Result<NonNull<u8>, AllocError> {
495        ralloc(self, ptr, old_layout, new_layout, AllocPattern::Zeroed)
496    }
497}
498
499#[cfg(feature = "nightly")]
500/// The primary module for when `nightly` is enabled.
501pub(crate) mod nightly {
502    use core::alloc::Layout;
503    // SAFETY: DefaultAlloc's allocated memory isn't deallocated until a deallocation method is
504    //  called. as a ZST allocator, copying/cloning it doesn't change behavior or invalidate
505    //  allocations.
506    unsafe impl alloc::alloc::Allocator for crate::DefaultAlloc {
507        #[cfg_attr(miri, track_caller)]
508        #[inline]
509        fn allocate(
510            &self,
511            layout: Layout
512        ) -> Result<core::ptr::NonNull<[u8]>, alloc::alloc::AllocError> {
513            alloc::alloc::Allocator::allocate(&alloc::alloc::Global, layout)
514        }
515
516        #[cfg_attr(miri, track_caller)]
517        #[inline]
518        fn allocate_zeroed(
519            &self,
520            layout: Layout
521        ) -> Result<core::ptr::NonNull<[u8]>, alloc::alloc::AllocError> {
522            alloc::alloc::Allocator::allocate_zeroed(&alloc::alloc::Global, layout)
523        }
524
525        #[cfg_attr(miri, track_caller)]
526        #[inline]
527        unsafe fn deallocate(&self, ptr: core::ptr::NonNull<u8>, layout: Layout) {
528            alloc::alloc::Allocator::deallocate(&alloc::alloc::Global, ptr.cast(), layout);
529        }
530
531        #[cfg_attr(miri, track_caller)]
532        #[inline]
533        unsafe fn grow(
534            &self,
535            ptr: core::ptr::NonNull<u8>,
536            old_layout: Layout,
537            new_layout: Layout
538        ) -> Result<core::ptr::NonNull<[u8]>, alloc::alloc::AllocError> {
539            alloc::alloc::Allocator::grow(&alloc::alloc::Global, ptr.cast(), old_layout, new_layout)
540        }
541
542        #[cfg_attr(miri, track_caller)]
543        #[inline]
544        unsafe fn grow_zeroed(
545            &self,
546            ptr: core::ptr::NonNull<u8>,
547            old_layout: Layout,
548            new_layout: Layout
549        ) -> Result<core::ptr::NonNull<[u8]>, alloc::alloc::AllocError> {
550            alloc::alloc::Allocator::grow_zeroed(
551                &alloc::alloc::Global,
552                ptr.cast(),
553                old_layout,
554                new_layout
555            )
556        }
557
558        #[cfg_attr(miri, track_caller)]
559        #[inline]
560        unsafe fn shrink(
561            &self,
562            ptr: core::ptr::NonNull<u8>,
563            old_layout: Layout,
564            new_layout: Layout
565        ) -> Result<core::ptr::NonNull<[u8]>, alloc::alloc::AllocError> {
566            alloc::alloc::Allocator::shrink(
567                &alloc::alloc::Global,
568                ptr.cast(),
569                old_layout,
570                new_layout
571            )
572        }
573    }
574
575    default_alloc_impl!(alloc::alloc::Global);
576}
577
578#[allow(clippy::inline_always)]
579impl<A: Alloc + ?Sized> Alloc for &A {
580    #[cfg_attr(miri, track_caller)]
581    #[inline(always)]
582    fn alloc(&self, layout: Layout) -> Result<NonNull<u8>, AllocError> {
583        (**self).alloc(layout)
584    }
585
586    #[cfg_attr(miri, track_caller)]
587    #[inline(always)]
588    fn zalloc(&self, layout: Layout) -> Result<NonNull<u8>, AllocError> {
589        (**self).zalloc(layout)
590    }
591
592    #[cfg_attr(miri, track_caller)]
593    #[inline(always)]
594    unsafe fn dealloc(&self, ptr: NonNull<u8>, layout: Layout) {
595        (**self).dealloc(ptr, layout);
596    }
597}
598
599#[cfg(feature = "std")]
600impl Alloc for std::alloc::System {
601    #[cfg_attr(miri, track_caller)]
602    #[inline]
603    fn alloc(&self, layout: Layout) -> Result<NonNull<u8>, AllocError> {
604        helpers::null_q_zsl_check(
605            layout,
606            // SAFETY: System::alloc is only called after the layout is verified non-zero-sized.
607            |layout| unsafe { alloc::alloc::GlobalAlloc::alloc(self, layout) },
608            helpers::null_q_dyn
609        )
610    }
611
612    #[cfg_attr(miri, track_caller)]
613    #[inline]
614    fn zalloc(&self, layout: Layout) -> Result<NonNull<u8>, AllocError> {
615        helpers::null_q_zsl_check(
616            layout,
617            // SAFETY: System::alloc_zeroed is only called after the layout is verified
618            //  non-zero-sized.
619            |layout| unsafe { alloc::alloc::GlobalAlloc::alloc_zeroed(self, layout) },
620            helpers::null_q_dyn
621        )
622    }
623
624    #[cfg_attr(miri, track_caller)]
625    #[inline]
626    unsafe fn dealloc(&self, ptr: NonNull<u8>, layout: Layout) {
627        if layout.size() != 0 {
628            alloc::alloc::GlobalAlloc::dealloc(self, ptr.as_ptr(), layout);
629        }
630    }
631}
632
633/// Internal helper to grow the allocation at `ptr` by deallocating using `old_layout` and
634/// reallocating using `new_layout`, filling new bytes using `pattern.`
635#[allow(clippy::missing_errors_doc, clippy::missing_safety_doc)]
636#[cfg_attr(not(feature = "dev"), doc(hidden))]
637#[cfg_attr(miri, track_caller)]
638pub unsafe fn grow<A: Alloc + ?Sized>(
639    a: &A,
640    ptr: NonNull<u8>,
641    old_layout: Layout,
642    new_layout: Layout,
643    pattern: AllocPattern
644) -> Result<NonNull<u8>, AllocError> {
645    match old_layout.size().cmp(&new_layout.size()) {
646        Ordering::Less => grow_unchecked(a, ptr, old_layout, new_layout, pattern),
647        Ordering::Equal => {
648            if new_layout.align() == old_layout.align() {
649                Ok(ptr)
650            } else {
651                grow_unchecked(&a, ptr, old_layout, new_layout, pattern)
652            }
653        }
654        Ordering::Greater => Err(AllocError::grow_smaller(old_layout.size(), new_layout.size()))
655    }
656}
657
658/// Internal helper to shrink the allocation at `ptr` by deallocating using `old_layout` and
659/// reallocating using `new_layout`.
660#[allow(clippy::missing_errors_doc, clippy::missing_safety_doc)]
661#[cfg_attr(not(feature = "dev"), doc(hidden))]
662#[cfg_attr(miri, track_caller)]
663pub unsafe fn shrink<A: Alloc + ?Sized>(
664    a: &A,
665    ptr: NonNull<u8>,
666    old_layout: Layout,
667    new_layout: Layout
668) -> Result<NonNull<u8>, AllocError> {
669    match old_layout.size().cmp(&new_layout.size()) {
670        Ordering::Less => Err(AllocError::shrink_larger(old_layout.size(), new_layout.size())),
671        Ordering::Equal => {
672            if new_layout.align() == old_layout.align() {
673                Ok(ptr)
674            } else {
675                shrink_unchecked(&a, ptr, old_layout, new_layout)
676            }
677        }
678        Ordering::Greater => shrink_unchecked(a, ptr, old_layout, new_layout)
679    }
680}
681
682/// Internal helper to grow the allocation at `ptr` by deallocating using `old_layout` and
683/// reallocating using `new_layout`.
684///
685/// # Safety
686///
687/// This function doesn't check for layout validity.
688/// Callers must ensure `new_layout.size()` is greater than `old_layout.size()`.
689#[allow(clippy::needless_pass_by_value)]
690#[cfg_attr(miri, track_caller)]
691unsafe fn grow_unchecked<A: Alloc + ?Sized>(
692    a: &A,
693    ptr: NonNull<u8>,
694    old_layout: Layout,
695    new_layout: Layout,
696    pattern: AllocPattern
697) -> Result<NonNull<u8>, AllocError> {
698    let old_size = old_layout.size();
699    let new_ptr = match pattern {
700        AllocPattern::Uninitialized => tri!(do a.alloc(new_layout)),
701        AllocPattern::Zeroed => tri!(do a.zalloc(new_layout)),
702        AllocPattern::Shrink => core::hint::unreachable_unchecked()
703    };
704
705    ptr::copy_nonoverlapping(ptr.as_ptr(), new_ptr.as_ptr(), old_size);
706    if old_size != 0 {
707        a.dealloc(ptr, old_layout);
708    }
709
710    Ok(new_ptr)
711}
712
713/// Internal helper to shrink the allocation at `ptr` by deallocating using `old_layout` and
714/// reallocating using `new_layout`.
715///
716/// # Safety
717///
718/// This function doesn't check for layout validity.
719/// Callers must ensure `new_layout.size()` is greater than `old_layout.size()`.
720#[cfg_attr(miri, track_caller)]
721unsafe fn shrink_unchecked<A: Alloc + ?Sized>(
722    a: &A,
723    ptr: NonNull<u8>,
724    old_layout: Layout,
725    new_layout: Layout
726) -> Result<NonNull<u8>, AllocError> {
727    let new_ptr = tri!(do a.alloc(new_layout));
728
729    ptr::copy_nonoverlapping(ptr.as_ptr(), new_ptr.as_ptr(), new_layout.size());
730    if old_layout.size() != 0 {
731        a.dealloc(ptr, old_layout);
732    }
733
734    Ok(new_ptr)
735}
736
737/// Helper for realloc to reduce repetition.
738#[allow(clippy::missing_errors_doc, clippy::missing_safety_doc)]
739#[cfg_attr(not(feature = "dev"), doc(hidden))]
740#[cfg_attr(miri, track_caller)]
741pub unsafe fn ralloc<A: Alloc + ?Sized>(
742    a: &A,
743    ptr: NonNull<u8>,
744    old_layout: Layout,
745    new_layout: Layout,
746    pat: AllocPattern
747) -> Result<NonNull<u8>, AllocError> {
748    match old_layout.size().cmp(&new_layout.size()) {
749        Ordering::Less => grow_unchecked(&a, ptr, old_layout, new_layout, pat),
750        Ordering::Greater => shrink_unchecked(&a, ptr, old_layout, new_layout),
751        Ordering::Equal => {
752            if new_layout.align() == old_layout.align() {
753                Ok(ptr)
754            } else {
755                grow_unchecked(&a, ptr, old_layout, new_layout, pat)
756            }
757        }
758    }
759}
760
761/// A byte pattern.
762///
763/// This is used to determine or represent the pattern new bytes will be or were filled with.
764#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
765#[cfg_attr(not(feature = "dev"), doc(hidden))]
766#[repr(u8)]
767pub enum AllocPattern {
768    /// Uninitialized bytes.
769    Uninitialized,
770    /// Zeroed bytes.
771    Zeroed,
772    /// No new bytes.
773    Shrink
774}
775
776/// Helpers that tend to be useful in other libraries as well.
777pub mod helpers;