Skip to main content

bump_scope/
bump_scope.rs

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