Skip to main content

bump_scope/
bump_pool.rs

1use std::{
2    alloc::Layout,
3    mem::{self, ManuallyDrop},
4    ops::{Deref, DerefMut},
5    sync::{Mutex, MutexGuard, PoisonError},
6    vec::Vec,
7};
8
9use crate::{
10    Bump, BumpScope, ErrorBehavior,
11    alloc::{AllocError, Allocator},
12    maybe_default_allocator,
13    settings::{BumpAllocatorSettings, BumpSettings},
14};
15
16#[cfg(feature = "panic-on-alloc")]
17use crate::panic_on_error;
18
19macro_rules! make_pool {
20    ($($allocator_parameter:tt)*) => {
21        /// A pool of bump allocators.
22        ///
23        /// This type allows bump allocations in parallel, with the allocations' lifetimes tied to the pool.
24        ///
25        /// # Examples
26        ///
27        /// Using `BumpPool` with parallel iterators from [`rayon`](https://docs.rs/rayon):
28        /// ```
29        /// # use bump_scope::BumpPool;
30        /// # use rayon::prelude::{ParallelIterator, IntoParallelIterator};
31        /// # if cfg!(miri) { return } // rayon violates strict-provenance :(
32        /// #
33        /// let mut pool: BumpPool = BumpPool::new();
34        ///
35        /// let strings: Vec<&str> = (0..1000)
36        ///     .into_par_iter()
37        ///     .map_init(|| pool.get(), |bump, i| {
38        ///         // do some expensive work
39        ///         bump.alloc_fmt(format_args!("{i}")).into_ref()
40        ///     })
41        ///     .collect();
42        ///
43        /// dbg!(&strings);
44        ///
45        /// pool.reset();
46        ///
47        /// // memory of the strings is freed, trying to access `strings` will result in a lifetime error
48        /// // dbg!(&strings);
49        /// ```
50        ///
51        /// Using `BumpPool` with [`std::thread::scope`]:
52        /// ```
53        /// # use bump_scope::BumpPool;
54        /// let pool: BumpPool = BumpPool::new();
55        /// let (sender, receiver) = std::sync::mpsc::sync_channel(10);
56        ///
57        /// std::thread::scope(|s| {
58        ///     s.spawn(|| {
59        ///         let bump = pool.get();
60        ///         let string = bump.alloc_str("Hello");
61        ///         sender.send(string).unwrap();
62        ///         drop(sender);
63        ///     });
64        ///
65        ///     s.spawn(|| {
66        ///         for string in receiver {
67        ///             assert_eq!(string, "Hello");
68        ///         }
69        ///     });
70        /// });
71        /// ```
72        ///
73        #[doc(alias = "Herd")]
74        #[derive(Debug)]
75        pub struct BumpPool<$($allocator_parameter)*, S = BumpSettings>
76        where
77            A: Allocator,
78            S: BumpAllocatorSettings,
79        {
80            bumps: Mutex<Vec<Bump<A, S>>>,
81            allocator: A,
82        }
83    };
84}
85
86maybe_default_allocator!(make_pool);
87
88impl<A, S> Default for BumpPool<A, S>
89where
90    A: Allocator + Default,
91    S: BumpAllocatorSettings,
92{
93    fn default() -> Self {
94        Self {
95            bumps: Mutex::default(),
96            allocator: Default::default(),
97        }
98    }
99}
100
101impl<A, S> BumpPool<A, S>
102where
103    A: Allocator + Default,
104    S: BumpAllocatorSettings,
105{
106    /// Constructs a new `BumpPool`.
107    #[inline]
108    #[must_use]
109    pub fn new() -> Self {
110        Self::default()
111    }
112}
113
114impl<A, S> BumpPool<A, S>
115where
116    A: Allocator,
117    S: BumpAllocatorSettings,
118{
119    /// Constructs a new `BumpPool` with the provided allocator.
120    #[inline]
121    #[must_use]
122    pub const fn new_in(allocator: A) -> Self {
123        Self {
124            bumps: Mutex::new(Vec::new()),
125            allocator,
126        }
127    }
128
129    /// [`reset`](Bump::reset) all `Bump`s in this pool.
130    pub fn reset(&mut self) {
131        for bump in self.bumps() {
132            bump.reset();
133        }
134    }
135
136    /// [`reset_to_start`](Bump::reset_to_start) all `Bump`s in this pool.
137    pub fn reset_to_start(&mut self) {
138        for bump in self.bumps() {
139            bump.reset_to_start();
140        }
141    }
142
143    /// Returns the vector of `Bump`s.
144    pub fn bumps(&mut self) -> &mut Vec<Bump<A, S>> {
145        self.bumps.get_mut().unwrap_or_else(PoisonError::into_inner)
146    }
147
148    fn lock(&self) -> MutexGuard<'_, Vec<Bump<A, S>>> {
149        self.bumps.lock().unwrap_or_else(PoisonError::into_inner)
150    }
151}
152
153impl<A, S> BumpPool<A, S>
154where
155    A: Allocator + Clone,
156    S: BumpAllocatorSettings,
157{
158    /// Borrows a bump allocator from the pool.
159    /// With this `BumpPoolGuard` you can make allocations that live for as long as the pool lives.
160    ///
161    /// If this needs to create a new `Bump`, it will be constructed by calling <code>Bump::[new]\()</code>.
162    ///
163    /// [new]: Bump::new
164    ///
165    /// # Panics
166    /// Panics if the allocation fails.
167    #[must_use]
168    #[inline(always)]
169    #[cfg(feature = "panic-on-alloc")]
170    pub fn get(&self) -> BumpPoolGuard<'_, A, S> {
171        let bump = match self.lock().pop() {
172            Some(bump) => bump,
173            None => Bump::new_in(self.allocator.clone()),
174        };
175
176        BumpPoolGuard {
177            pool: self,
178            bump: ManuallyDrop::new(bump),
179        }
180    }
181
182    /// Borrows a bump allocator from the pool.
183    /// With this `BumpPoolGuard` you can make allocations that live for as long as the pool lives.
184    ///
185    /// If this needs to create a new `Bump`, it will be constructed by calling <code>Bump::[try_new]\()</code>.
186    ///
187    /// [try_new]: Bump::try_new
188    ///
189    /// # Errors
190    /// Errors if the allocation fails.
191    #[inline(always)]
192    pub fn try_get(&self) -> Result<BumpPoolGuard<'_, A, S>, AllocError> {
193        let bump = match self.lock().pop() {
194            Some(bump) => bump,
195            None => Bump::try_new_in(self.allocator.clone())?,
196        };
197
198        Ok(BumpPoolGuard {
199            pool: self,
200            bump: ManuallyDrop::new(bump),
201        })
202    }
203
204    /// Borrows a bump allocator from the pool.
205    /// With this `BumpPoolGuard` you can make allocations that live for as long as the pool lives.
206    ///
207    ///  If this needs to create a new `Bump`, it will be constructed by calling <code>Bump::[with_size]\(size)</code>.
208    ///
209    /// [with_size]: Bump::with_size
210    ///
211    /// # Panics
212    /// Panics if the allocation fails.
213    #[must_use]
214    #[inline(always)]
215    #[cfg(feature = "panic-on-alloc")]
216    pub fn get_with_size(&self, size: usize) -> BumpPoolGuard<'_, A, S> {
217        panic_on_error(self.generic_get_with_size(size))
218    }
219
220    /// Borrows a bump allocator from the pool.
221    /// With this `BumpPoolGuard` you can make allocations that live for as long as the pool lives.
222    ///
223    ///  If this needs to create a new `Bump`, it will be constructed by calling <code>Bump::[try_with_size]\(size)</code>.
224    ///
225    /// [try_with_size]: Bump::try_with_size
226    ///
227    /// # Errors
228    /// Errors if the allocation fails.
229    #[inline(always)]
230    pub fn try_get_with_size(&self, size: usize) -> Result<BumpPoolGuard<'_, A, S>, AllocError> {
231        self.generic_get_with_size(size)
232    }
233
234    pub(crate) fn generic_get_with_size<E: ErrorBehavior>(&self, size: usize) -> Result<BumpPoolGuard<'_, A, S>, E> {
235        let bump = match self.lock().pop() {
236            Some(bump) => bump,
237            None => Bump::generic_with_size_in(size, self.allocator.clone())?,
238        };
239
240        Ok(BumpPoolGuard {
241            pool: self,
242            bump: ManuallyDrop::new(bump),
243        })
244    }
245
246    /// Borrows a bump allocator from the pool.
247    /// With this `BumpPoolGuard` you can make allocations that live for as long as the pool lives.
248    ///
249    /// If this needs to create a new `Bump`, it will be constructed by calling <code>Bump::[with_capacity]\(layout)</code>.
250    ///
251    /// [with_capacity]: Bump::with_capacity
252    ///
253    /// # Panics
254    /// Panics if the allocation fails.
255    #[must_use]
256    #[inline(always)]
257    #[cfg(feature = "panic-on-alloc")]
258    pub fn get_with_capacity(&self, layout: Layout) -> BumpPoolGuard<'_, A, S> {
259        panic_on_error(self.generic_get_with_capacity(layout))
260    }
261
262    /// Borrows a bump allocator from the pool.
263    /// With this `BumpPoolGuard` you can make allocations that live for as long as the pool lives.
264    ///
265    ///  If this needs to create a new `Bump`, it will be constructed by calling <code>Bump::[try_with_capacity]\(layout)</code>.
266    ///
267    /// [try_with_capacity]: Bump::try_with_capacity
268    ///
269    /// # Errors
270    /// Errors if the allocation fails.
271    #[inline(always)]
272    pub fn try_get_with_capacity(&self, layout: Layout) -> Result<BumpPoolGuard<'_, A, S>, AllocError> {
273        self.generic_get_with_capacity(layout)
274    }
275
276    pub(crate) fn generic_get_with_capacity<E: ErrorBehavior>(&self, layout: Layout) -> Result<BumpPoolGuard<'_, A, S>, E> {
277        let bump = match self.lock().pop() {
278            Some(bump) => bump,
279            None => Bump::generic_with_capacity_in(layout, self.allocator.clone())?,
280        };
281
282        Ok(BumpPoolGuard {
283            pool: self,
284            bump: ManuallyDrop::new(bump),
285        })
286    }
287}
288
289macro_rules! make_pool_guard {
290    ($($allocator_parameter:tt)*) => {
291
292        /// This is a wrapper around [`Bump`] that mutably derefs to a [`BumpScope`] and returns its [`Bump`] back to the [`BumpPool`] on drop.
293        #[derive(Debug)]
294        pub struct BumpPoolGuard<'a, $($allocator_parameter)*, S = BumpSettings>
295        where
296            A: Allocator,
297            S: BumpAllocatorSettings,
298        {
299            bump: ManuallyDrop<Bump<A, S>>,
300            pool: &'a BumpPool<A, S>,
301        }
302    };
303}
304
305maybe_default_allocator!(make_pool_guard);
306
307impl<'a, A, S> BumpPoolGuard<'a, A, S>
308where
309    A: Allocator,
310    S: BumpAllocatorSettings,
311{
312    /// The [`BumpPool`], this [`BumpPoolGuard`] was created from.
313    pub fn pool(&self) -> &'a BumpPool<A, S> {
314        self.pool
315    }
316}
317
318impl<'a, A, S> Deref for BumpPoolGuard<'a, A, S>
319where
320    A: Allocator,
321    S: BumpAllocatorSettings,
322{
323    type Target = BumpScope<'a, A, S>;
324
325    #[inline(always)]
326    fn deref(&self) -> &Self::Target {
327        unsafe { transmute_lifetime(self.bump.as_scope()) }
328    }
329}
330
331impl<A, S> DerefMut for BumpPoolGuard<'_, A, S>
332where
333    A: Allocator,
334    S: BumpAllocatorSettings,
335{
336    #[inline(always)]
337    fn deref_mut(&mut self) -> &mut Self::Target {
338        unsafe { transmute_lifetime_mut(self.bump.as_mut_scope()) }
339    }
340}
341
342impl<A, S> Drop for BumpPoolGuard<'_, A, S>
343where
344    A: Allocator,
345    S: BumpAllocatorSettings,
346{
347    fn drop(&mut self) {
348        let bump = unsafe { ManuallyDrop::take(&mut self.bump) };
349        self.pool.lock().push(bump);
350    }
351}
352
353// This exists as a "safer" transmute that only transmutes the `'a` lifetime parameter.
354#[expect(clippy::elidable_lifetime_names)]
355unsafe fn transmute_lifetime<'from, 'to, 'b, A, S>(scope: &'b BumpScope<'from, A, S>) -> &'b BumpScope<'to, A, S>
356where
357    S: BumpAllocatorSettings,
358{
359    unsafe { mem::transmute(scope) }
360}
361
362// This exists as a "safer" transmute that only transmutes the `'a` lifetime parameter.
363#[expect(clippy::elidable_lifetime_names)]
364unsafe fn transmute_lifetime_mut<'from, 'to, 'b, A, S>(scope: &'b mut BumpScope<'from, A, S>) -> &'b mut BumpScope<'to, A, S>
365where
366    S: BumpAllocatorSettings,
367{
368    unsafe { mem::transmute(scope) }
369}