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}