Skip to main content

bump_scope/
bump_scope.rs

1use core::{
2    alloc::Layout,
3    ffi::CStr,
4    fmt::{self, Debug},
5    marker::PhantomData,
6    mem::MaybeUninit,
7    panic::{RefUnwindSafe, UnwindSafe},
8    ptr::NonNull,
9};
10
11#[cfg(feature = "nightly-clone-to-uninit")]
12use core::clone::CloneToUninit;
13
14use crate::{
15    BaseAllocator, BumpBox, BumpClaimGuard, BumpScopeGuard, Checkpoint, ErrorBehavior, NoDrop, SizedTypeProperties,
16    alloc::{AllocError, Allocator},
17    allocator_impl, down_align_usize, maybe_default_allocator,
18    owned_slice::OwnedSlice,
19    polyfill::{non_null, transmute_mut, transmute_ref, transmute_value},
20    raw_bump::RawBump,
21    settings::{BumpAllocatorSettings, BumpSettings, MinimumAlignment, SupportedMinimumAlignment},
22    stats::{AnyStats, Stats},
23    traits::{
24        self, BumpAllocator, BumpAllocatorCore, BumpAllocatorScope, BumpAllocatorTyped, BumpAllocatorTypedScope,
25        MutBumpAllocatorTypedScope,
26    },
27    up_align_usize_unchecked,
28};
29
30#[cfg(feature = "alloc")]
31use crate::alloc::Global;
32
33#[cfg(feature = "panic-on-alloc")]
34use crate::panic_on_error;
35
36macro_rules! make_type {
37    ($($allocator_parameter:tt)*) => {
38        /// A bump allocation scope.
39        ///
40        /// A `BumpScope`'s allocations are live for `'a`, which is the lifetime of its associated `BumpScopeGuard` or `scoped` closure.
41        ///
42        /// `BumpScope` has mostly same api as [`Bump`].
43        ///
44        /// This type is provided as a parameter to the closure of [`scoped`], or created
45        /// by [`BumpScopeGuard::scope`]. A [`Bump`] can also be turned into a `BumpScope` using
46        /// [`as_scope`], [`as_mut_scope`] or `from` / `into`.
47        ///
48        /// [`scoped`]: crate::traits::BumpAllocator::scoped
49        /// [`BumpScopeGuard::scope`]: crate::BumpScopeGuard::scope
50        /// [`Bump`]: crate::Bump
51        /// [`as_scope`]: crate::Bump::as_scope
52        /// [`as_mut_scope`]: crate::Bump::as_mut_scope
53        /// [`reset`]: crate::Bump::reset
54        #[repr(transparent)]
55        pub struct BumpScope<'a, $($allocator_parameter)*, S = BumpSettings>
56        where
57            S: BumpAllocatorSettings,
58        {
59            pub(crate) raw: RawBump<A, S>,
60
61            /// Marks the lifetime of the mutably borrowed `BumpScopeGuard`.
62            pub(crate) marker: PhantomData<&'a ()>,
63        }
64    };
65}
66
67maybe_default_allocator!(make_type);
68
69impl<A, S> UnwindSafe for BumpScope<'_, A, S>
70where
71    A: RefUnwindSafe,
72    S: BumpAllocatorSettings,
73{
74}
75
76impl<A, S> RefUnwindSafe for BumpScope<'_, A, S>
77where
78    A: RefUnwindSafe,
79    S: BumpAllocatorSettings,
80{
81}
82
83impl<A, S> Debug for BumpScope<'_, A, S>
84where
85    S: BumpAllocatorSettings,
86{
87    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88        AnyStats::from(self.stats()).debug_format("BumpScope", f)
89    }
90}
91
92impl<A, S> BumpScope<'_, A, S>
93where
94    A: BaseAllocator<S::GuaranteedAllocated>,
95    S: BumpAllocatorSettings,
96{
97    /// Converts this `&mut BumpScope` into an owned `BumpScope`.
98    ///
99    /// Allocates a chunk if none has been allocated yet.
100    ///
101    /// This can be used to remove an indirection, simplify type signatures and allow
102    /// for more settings conversions via [`with_settings`].
103    ///
104    /// # Panics
105    /// Panics if the bump allocator is currently [claimed].
106    ///
107    /// Panics if the allocation fails.
108    ///
109    /// [claimed]: crate::traits::BumpAllocatorScope::claim
110    /// [`with_settings`]: BumpScope::with_settings
111    /// [`borrow_mut_with_settings`]: BumpScope::borrow_mut_with_settings
112    #[must_use]
113    #[inline(always)]
114    #[cfg(feature = "panic-on-alloc")]
115    pub fn by_value(&mut self) -> BumpScope<'_, A, S> {
116        panic_on_error(self.raw.make_allocated());
117
118        BumpScope {
119            raw: self.raw.clone(),
120            marker: PhantomData,
121        }
122    }
123
124    /// Converts this `&mut BumpScope` into an owned `BumpScope`.
125    ///
126    /// Allocates a chunk if none has been allocated yet.
127    ///
128    /// This can be used to remove an indirection, simplify type signatures and allow
129    /// for more settings conversions via [`with_settings`].
130    ///
131    /// # Errors
132    /// Errors if the bump allocator is currently [claimed].
133    ///
134    /// Errors if the allocation fails.
135    ///
136    /// [claimed]: crate::traits::BumpAllocatorScope::claim
137    /// [`with_settings`]: BumpScope::with_settings
138    /// [`borrow_mut_with_settings`]: BumpScope::borrow_mut_with_settings
139    #[inline(always)]
140    pub fn try_by_value(&mut self) -> Result<BumpScope<'_, A, S>, AllocError> {
141        self.raw.make_allocated::<AllocError>()?;
142
143        Ok(BumpScope {
144            raw: self.raw.clone(),
145            marker: PhantomData,
146        })
147    }
148}
149
150impl<'a, A, S> BumpScope<'a, A, S>
151where
152    S: BumpAllocatorSettings,
153{
154    /// Returns a type which provides statistics about the memory usage of the bump allocator.
155    #[must_use]
156    #[inline(always)]
157    pub fn stats(&self) -> Stats<'a, A, S> {
158        self.raw.stats()
159    }
160
161    #[inline(always)]
162    pub(crate) fn align<const ALIGN: usize>(&self)
163    where
164        MinimumAlignment<ALIGN>: SupportedMinimumAlignment,
165    {
166        self.raw.align::<ALIGN>();
167    }
168
169    /// Converts this `BumpScope` into a `BumpScope` with new settings.
170    ///
171    /// This function will fail to compile if:
172    /// - `NewS::MIN_ALIGN < S::MIN_ALIGN`
173    /// - `NewS::UP != S::UP`
174    ///
175    /// # Panics
176    /// Panics if `!NewS::CLAIMABLE` and the bump allocator is currently [claimed].
177    ///
178    /// [claimed]: crate::traits::BumpAllocatorScope::claim
179    #[inline]
180    pub fn with_settings<NewS>(self) -> BumpScope<'a, A, NewS>
181    where
182        NewS: BumpAllocatorSettings,
183    {
184        self.raw.ensure_scope_satisfies_settings::<NewS>();
185        unsafe { transmute_value(self) }
186    }
187
188    /// Borrows this `BumpScope` with new settings.
189    ///
190    /// This function will fail to compile if:
191    /// - `NewS::MIN_ALIGN != S::MIN_ALIGN`
192    /// - `NewS::UP != S::UP`
193    /// - `NewS::CLAIMABLE != S::CLAIMABLE`
194    /// - `NewS::GUARANTEED_ALLOCATED > S::GUARANTEED_ALLOCATED`
195    #[inline]
196    pub fn borrow_with_settings<NewS>(&self) -> &BumpScope<'a, A, NewS>
197    where
198        NewS: BumpAllocatorSettings,
199    {
200        self.raw.ensure_satisfies_settings_for_borrow::<NewS>();
201        unsafe { transmute_ref(self) }
202    }
203
204    /// Borrows this `BumpScope` mutably with new settings.
205    ///
206    /// This function will fail to compile if:
207    /// - `NewS::MIN_ALIGN < S::MIN_ALIGN`
208    /// - `NewS::UP != S::UP`
209    /// - `NewS::GUARANTEED_ALLOCATED != S::GUARANTEED_ALLOCATED`
210    /// - `NewS::CLAIMABLE != S::CLAIMABLE`
211    #[inline]
212    pub fn borrow_mut_with_settings<NewS>(&mut self) -> &mut BumpScope<'a, A, NewS>
213    where
214        NewS: BumpAllocatorSettings,
215    {
216        self.raw.ensure_satisfies_settings_for_borrow_mut::<NewS>();
217        unsafe { transmute_mut(self) }
218    }
219}
220
221#[cfg(feature = "alloc")]
222impl<S> BumpScope<'_, Global, S>
223where
224    S: BumpAllocatorSettings,
225{
226    /// Converts this `BumpScope` into a raw pointer.
227    #[inline]
228    #[must_use]
229    pub fn into_raw(self) -> NonNull<()> {
230        self.raw.into_raw()
231    }
232
233    /// Converts the raw pointer that was created with [`into_raw`](Self::into_raw) back into a `BumpScope`.
234    ///
235    /// # Safety
236    /// This is highly unsafe, due to the number of invariants that aren't checked:
237    /// - `ptr` must have been created with `Self::into_raw`.
238    /// - This function must only be called once with this `ptr`.
239    /// - Nothing must have been allocated since then.
240    /// - The lifetime must match the original one.
241    /// - The settings must match the original ones.
242    #[inline]
243    #[must_use]
244    pub unsafe fn from_raw(ptr: NonNull<()>) -> Self {
245        Self {
246            raw: unsafe { RawBump::from_raw(ptr) },
247            marker: PhantomData,
248        }
249    }
250}
251
252impl<A, S> NoDrop for BumpScope<'_, A, S> where S: BumpAllocatorSettings {}
253
254/// Methods that forward to traits.
255// Documentation is in the forwarded to methods.
256#[allow(clippy::missing_errors_doc, clippy::missing_safety_doc)]
257impl<'a, A, S> BumpScope<'a, A, S>
258where
259    A: BaseAllocator<S::GuaranteedAllocated>,
260    S: BumpAllocatorSettings,
261{
262    traits::forward_methods! {
263        self: self
264        access: {self}
265        access_mut: {self}
266        lifetime: 'a
267    }
268}
269
270/// Additional `alloc` methods that are not available in traits.
271impl<'a, A, S> BumpScope<'a, A, S>
272where
273    A: BaseAllocator<S::GuaranteedAllocated>,
274    S: BumpAllocatorSettings,
275{
276    /// Allocates the result of `f` in the bump allocator, then moves `E` out of it and deallocates the space it took up.
277    ///
278    /// This can be more performant than allocating `T` after the fact, as `Result<T, E>` may be constructed in the bump allocators memory instead of on the stack and then copied over.
279    ///
280    /// There is also [`alloc_try_with_mut`](Self::alloc_try_with_mut), optimized for a mutable reference.
281    ///
282    /// # Panics
283    /// Panics if the allocation fails.
284    ///
285    /// # Examples
286    #[cfg_attr(feature = "nightly-tests", doc = "```")]
287    #[cfg_attr(not(feature = "nightly-tests"), doc = "```ignore")]
288    /// # #![feature(offset_of_enum)]
289    /// # use core::mem::offset_of;
290    /// # use bump_scope::Bump;
291    /// # let bump: Bump = Bump::new();
292    /// let result = bump.alloc_try_with(|| -> Result<i32, i32> { Ok(123) });
293    /// assert_eq!(result.unwrap(), 123);
294    /// assert_eq!(bump.stats().allocated(), offset_of!(Result<i32, i32>, Ok.0) + size_of::<i32>());
295    /// ```
296    #[cfg_attr(feature = "nightly-tests", doc = "```")]
297    #[cfg_attr(not(feature = "nightly-tests"), doc = "```ignore")]
298    /// # use bump_scope::Bump;
299    /// # let bump: Bump = Bump::new();
300    /// let result = bump.alloc_try_with(|| -> Result<i32, i32> { Err(123) });
301    /// assert_eq!(result.unwrap_err(), 123);
302    /// assert_eq!(bump.stats().allocated(), 0);
303    /// ```
304    #[inline(always)]
305    #[cfg(feature = "panic-on-alloc")]
306    #[expect(clippy::missing_errors_doc)]
307    pub fn alloc_try_with<T, E>(&self, f: impl FnOnce() -> Result<T, E>) -> Result<BumpBox<'a, T>, E> {
308        panic_on_error(self.generic_alloc_try_with(f))
309    }
310
311    /// Allocates the result of `f` in the bump allocator, then moves `E` out of it and deallocates the space it took up.
312    ///
313    /// This can be more performant than allocating `T` after the fact, as `Result<T, E>` may be constructed in the bump allocators memory instead of on the stack and then copied over.
314    ///
315    /// There is also [`try_alloc_try_with_mut`](Self::try_alloc_try_with_mut), optimized for a mutable reference.
316    ///
317    /// # Errors
318    /// Errors if the allocation fails.
319    ///
320    /// # Examples
321    #[cfg_attr(feature = "nightly-tests", doc = "```")]
322    #[cfg_attr(not(feature = "nightly-tests"), doc = "```ignore")]
323    /// # #![feature(offset_of_enum)]
324    /// # use core::mem::offset_of;
325    /// # use bump_scope::Bump;
326    /// # let bump: Bump = Bump::new();
327    /// let result = bump.try_alloc_try_with(|| -> Result<i32, i32> { Ok(123) })?;
328    /// assert_eq!(result.unwrap(), 123);
329    /// assert_eq!(bump.stats().allocated(), offset_of!(Result<i32, i32>, Ok.0) + size_of::<i32>());
330    /// # Ok::<(), bump_scope::alloc::AllocError>(())
331    /// ```
332    #[cfg_attr(feature = "nightly-tests", doc = "```")]
333    #[cfg_attr(not(feature = "nightly-tests"), doc = "```ignore")]
334    /// # use bump_scope::Bump;
335    /// # let bump: Bump = Bump::new();
336    /// let result = bump.try_alloc_try_with(|| -> Result<i32, i32> { Err(123) })?;
337    /// assert_eq!(result.unwrap_err(), 123);
338    /// assert_eq!(bump.stats().allocated(), 0);
339    /// # Ok::<(), bump_scope::alloc::AllocError>(())
340    /// ```
341    #[inline(always)]
342    pub fn try_alloc_try_with<T, E>(
343        &self,
344        f: impl FnOnce() -> Result<T, E>,
345    ) -> Result<Result<BumpBox<'a, T>, E>, AllocError> {
346        self.generic_alloc_try_with(f)
347    }
348
349    #[inline(always)]
350    pub(crate) fn generic_alloc_try_with<B: ErrorBehavior, T, E>(
351        &self,
352        f: impl FnOnce() -> Result<T, E>,
353    ) -> Result<Result<BumpBox<'a, T>, E>, B> {
354        if T::IS_ZST {
355            return match f() {
356                Ok(value) => Ok(Ok(BumpBox::zst(value))),
357                Err(error) => Ok(Err(error)),
358            };
359        }
360
361        let checkpoint_before_alloc = self.checkpoint();
362        let uninit = self.generic_alloc_uninit::<B, Result<T, E>>()?;
363        let ptr = BumpBox::into_raw(uninit).cast::<Result<T, E>>();
364
365        // When bumping downwards the chunk's position is the same as `ptr`.
366        // Using `ptr` is faster so we use that.
367        let pos = if S::UP { self.raw.chunk.get().pos() } else { ptr.cast() };
368
369        Ok(unsafe {
370            non_null::write_with(ptr, f);
371
372            // If `f` made allocations on this bump allocator we can't shrink the allocation.
373            let can_shrink = pos == self.raw.chunk.get().pos();
374
375            match non_null::result(ptr) {
376                Ok(value) => Ok({
377                    if can_shrink {
378                        let new_pos = if S::UP {
379                            let pos = value.add(1).addr().get();
380                            up_align_usize_unchecked(pos, S::MIN_ALIGN)
381                        } else {
382                            let pos = value.addr().get();
383                            down_align_usize(pos, S::MIN_ALIGN)
384                        };
385
386                        // The allocation of was successful, so our chunk must be allocated.
387                        let chunk = self.raw.chunk.get().as_non_dummy_unchecked();
388                        chunk.set_pos_addr(new_pos);
389                    }
390
391                    BumpBox::from_raw(value)
392                }),
393                Err(error) => Err({
394                    let error = error.read();
395
396                    if can_shrink {
397                        self.reset_to(checkpoint_before_alloc);
398                    }
399
400                    error
401                }),
402            }
403        })
404    }
405
406    /// Allocates the result of `f` in the bump allocator, then moves `E` out of it and deallocates the space it took up.
407    ///
408    /// This can be more performant than allocating `T` after the fact, as `Result<T, E>` may be constructed in the bump allocators memory instead of on the stack and then copied over.
409    ///
410    /// This is just like [`alloc_try_with`](Self::alloc_try_with), but optimized for a mutable reference.
411    ///
412    /// # Panics
413    /// Panics if the allocation fails.
414    ///
415    /// # Examples
416    #[cfg_attr(feature = "nightly-tests", doc = "```")]
417    #[cfg_attr(not(feature = "nightly-tests"), doc = "```ignore")]
418    /// # #![feature(offset_of_enum)]
419    /// # use core::mem::offset_of;
420    /// # use bump_scope::Bump;
421    /// # let mut bump: Bump = Bump::new();
422    /// let result = bump.alloc_try_with_mut(|| -> Result<i32, i32> { Ok(123) });
423    /// assert_eq!(result.unwrap(), 123);
424    /// assert_eq!(bump.stats().allocated(), offset_of!(Result<i32, i32>, Ok.0) + size_of::<i32>());
425    /// ```
426    #[cfg_attr(feature = "nightly-tests", doc = "```")]
427    #[cfg_attr(not(feature = "nightly-tests"), doc = "```ignore")]
428    /// # use bump_scope::Bump;
429    /// # let mut bump: Bump = Bump::new();
430    /// let result = bump.alloc_try_with_mut(|| -> Result<i32, i32> { Err(123) });
431    /// assert_eq!(result.unwrap_err(), 123);
432    /// assert_eq!(bump.stats().allocated(), 0);
433    /// ```
434    #[inline(always)]
435    #[cfg(feature = "panic-on-alloc")]
436    #[expect(clippy::missing_errors_doc)]
437    pub fn alloc_try_with_mut<T, E>(&mut self, f: impl FnOnce() -> Result<T, E>) -> Result<BumpBox<'a, T>, E> {
438        panic_on_error(self.generic_alloc_try_with_mut(f))
439    }
440
441    /// Allocates the result of `f` in the bump allocator, then moves `E` out of it and deallocates the space it took up.
442    ///
443    /// This can be more performant than allocating `T` after the fact, as `Result<T, E>` may be constructed in the bump allocators memory instead of on the stack and then copied over.
444    ///
445    /// This is just like [`try_alloc_try_with`](Self::try_alloc_try_with), but optimized for a mutable reference.
446    ///
447    /// # Errors
448    /// Errors if the allocation fails.
449    ///
450    /// # Examples
451    #[cfg_attr(feature = "nightly-tests", doc = "```")]
452    #[cfg_attr(not(feature = "nightly-tests"), doc = "```ignore")]
453    /// # #![feature(offset_of_enum)]
454    /// # use core::mem::offset_of;
455    /// # use bump_scope::Bump;
456    /// # let mut bump: Bump = Bump::new();
457    /// let result = bump.try_alloc_try_with_mut(|| -> Result<i32, i32> { Ok(123) })?;
458    /// assert_eq!(result.unwrap(), 123);
459    /// assert_eq!(bump.stats().allocated(), offset_of!(Result<i32, i32>, Ok.0) + size_of::<i32>());
460    /// # Ok::<(), bump_scope::alloc::AllocError>(())
461    /// ```
462    #[cfg_attr(feature = "nightly-tests", doc = "```")]
463    #[cfg_attr(not(feature = "nightly-tests"), doc = "```ignore")]
464    /// # use bump_scope::Bump;
465    /// # let mut bump: Bump = Bump::new();
466    /// let result = bump.try_alloc_try_with_mut(|| -> Result<i32, i32> { Err(123) })?;
467    /// assert_eq!(result.unwrap_err(), 123);
468    /// assert_eq!(bump.stats().allocated(), 0);
469    /// # Ok::<(), bump_scope::alloc::AllocError>(())
470    /// ```
471    #[inline(always)]
472    pub fn try_alloc_try_with_mut<T, E>(
473        &mut self,
474        f: impl FnOnce() -> Result<T, E>,
475    ) -> Result<Result<BumpBox<'a, T>, E>, AllocError> {
476        self.generic_alloc_try_with_mut(f)
477    }
478
479    #[inline(always)]
480    pub(crate) fn generic_alloc_try_with_mut<B: ErrorBehavior, T, E>(
481        &mut self,
482        f: impl FnOnce() -> Result<T, E>,
483    ) -> Result<Result<BumpBox<'a, T>, E>, B> {
484        if T::IS_ZST {
485            return match f() {
486                Ok(value) => Ok(Ok(BumpBox::zst(value))),
487                Err(error) => Ok(Err(error)),
488            };
489        }
490
491        let checkpoint = self.checkpoint();
492        let ptr = self.raw.prepare_sized_allocation::<B, Result<T, E>>()?;
493
494        Ok(unsafe {
495            non_null::write_with(ptr, f);
496
497            // There is no need for `can_shrink` checks, because we have a mutable reference
498            // so there's no way anyone else has allocated in `f`.
499            match non_null::result(ptr) {
500                Ok(value) => Ok({
501                    let new_pos = if S::UP {
502                        let pos = value.add(1).addr().get();
503                        up_align_usize_unchecked(pos, S::MIN_ALIGN)
504                    } else {
505                        let pos = value.addr().get();
506                        down_align_usize(pos, S::MIN_ALIGN)
507                    };
508
509                    // The allocation was successful, so our chunk must be allocated.
510                    let chunk = self.raw.chunk.get().as_non_dummy_unchecked();
511                    chunk.set_pos_addr(new_pos);
512
513                    BumpBox::from_raw(value)
514                }),
515                Err(error) => Err({
516                    let error = error.read();
517                    self.reset_to(checkpoint);
518                    error
519                }),
520            }
521        })
522    }
523
524    #[inline(always)]
525    pub(crate) fn generic_alloc_uninit<B: ErrorBehavior, T>(&self) -> Result<BumpBox<'a, MaybeUninit<T>>, B> {
526        if T::IS_ZST {
527            return Ok(BumpBox::zst(MaybeUninit::uninit()));
528        }
529
530        let ptr = self.raw.alloc_sized::<B, T>()?.cast::<MaybeUninit<T>>();
531        unsafe { Ok(BumpBox::from_raw(ptr)) }
532    }
533}
534
535unsafe impl<A, S> Allocator for BumpScope<'_, A, S>
536where
537    A: BaseAllocator<S::GuaranteedAllocated>,
538    S: BumpAllocatorSettings,
539{
540    #[inline(always)]
541    fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
542        allocator_impl::allocate(&self.raw, layout)
543    }
544
545    #[inline(always)]
546    unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
547        unsafe { allocator_impl::deallocate(&self.raw, ptr, layout) };
548    }
549
550    #[inline(always)]
551    unsafe fn grow(&self, ptr: NonNull<u8>, old_layout: Layout, new_layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
552        unsafe { allocator_impl::grow(&self.raw, ptr, old_layout, new_layout) }
553    }
554
555    #[inline(always)]
556    unsafe fn grow_zeroed(
557        &self,
558        ptr: NonNull<u8>,
559        old_layout: Layout,
560        new_layout: Layout,
561    ) -> Result<NonNull<[u8]>, AllocError> {
562        unsafe { allocator_impl::grow_zeroed(&self.raw, ptr, old_layout, new_layout) }
563    }
564
565    #[inline(always)]
566    unsafe fn shrink(&self, ptr: NonNull<u8>, old_layout: Layout, new_layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
567        unsafe { allocator_impl::shrink(&self.raw, ptr, old_layout, new_layout) }
568    }
569}