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}