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}