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