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