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}