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    /// [Resets](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    /// Returns the vector of `Bump`s.
137    pub fn bumps(&mut self) -> &mut Vec<Bump<A, S>> {
138        self.bumps.get_mut().unwrap_or_else(PoisonError::into_inner)
139    }
140
141    fn lock(&self) -> MutexGuard<'_, Vec<Bump<A, S>>> {
142        self.bumps.lock().unwrap_or_else(PoisonError::into_inner)
143    }
144}
145
146impl<A, S> BumpPool<A, S>
147where
148    A: Allocator + Clone,
149    S: BumpAllocatorSettings,
150{
151    /// Borrows a bump allocator from the pool.
152    /// With this `BumpPoolGuard` you can make allocations that live for as long as the pool lives.
153    ///
154    /// If this needs to create a new `Bump`, it will be constructed by calling <code>Bump::[new]\()</code>.
155    ///
156    /// [new]: Bump::new
157    ///
158    /// # Panics
159    /// Panics if the allocation fails.
160    #[must_use]
161    #[inline(always)]
162    #[cfg(feature = "panic-on-alloc")]
163    pub fn get(&self) -> BumpPoolGuard<'_, A, S> {
164        let bump = match self.lock().pop() {
165            Some(bump) => bump,
166            None => Bump::new_in(self.allocator.clone()),
167        };
168
169        BumpPoolGuard {
170            pool: self,
171            bump: ManuallyDrop::new(bump),
172        }
173    }
174
175    /// Borrows a bump allocator from the pool.
176    /// With this `BumpPoolGuard` you can make allocations that live for as long as the pool lives.
177    ///
178    /// If this needs to create a new `Bump`, it will be constructed by calling <code>Bump::[try_new]\()</code>.
179    ///
180    /// [try_new]: Bump::try_new
181    ///
182    /// # Errors
183    /// Errors if the allocation fails.
184    #[inline(always)]
185    pub fn try_get(&self) -> Result<BumpPoolGuard<'_, A, S>, AllocError> {
186        let bump = match self.lock().pop() {
187            Some(bump) => bump,
188            None => Bump::try_new_in(self.allocator.clone())?,
189        };
190
191        Ok(BumpPoolGuard {
192            pool: self,
193            bump: ManuallyDrop::new(bump),
194        })
195    }
196
197    /// Borrows a bump allocator from the pool.
198    /// With this `BumpPoolGuard` you can make allocations that live for as long as the pool lives.
199    ///
200    ///  If this needs to create a new `Bump`, it will be constructed by calling <code>Bump::[with_size]\(size)</code>.
201    ///
202    /// [with_size]: Bump::with_size
203    ///
204    /// # Panics
205    /// Panics if the allocation fails.
206    #[must_use]
207    #[inline(always)]
208    #[cfg(feature = "panic-on-alloc")]
209    pub fn get_with_size(&self, size: usize) -> BumpPoolGuard<'_, A, S> {
210        panic_on_error(self.generic_get_with_size(size))
211    }
212
213    /// Borrows a bump allocator from the pool.
214    /// With this `BumpPoolGuard` you can make allocations that live for as long as the pool lives.
215    ///
216    ///  If this needs to create a new `Bump`, it will be constructed by calling <code>Bump::[try_with_size]\(size)</code>.
217    ///
218    /// [try_with_size]: Bump::try_with_size
219    ///
220    /// # Errors
221    /// Errors if the allocation fails.
222    #[inline(always)]
223    pub fn try_get_with_size(&self, size: usize) -> Result<BumpPoolGuard<'_, A, S>, AllocError> {
224        self.generic_get_with_size(size)
225    }
226
227    pub(crate) fn generic_get_with_size<E: ErrorBehavior>(&self, size: usize) -> Result<BumpPoolGuard<'_, A, S>, E> {
228        let bump = match self.lock().pop() {
229            Some(bump) => bump,
230            None => Bump::generic_with_size_in(size, self.allocator.clone())?,
231        };
232
233        Ok(BumpPoolGuard {
234            pool: self,
235            bump: ManuallyDrop::new(bump),
236        })
237    }
238
239    /// Borrows a bump allocator from the pool.
240    /// With this `BumpPoolGuard` you can make allocations that live for as long as the pool lives.
241    ///
242    /// If this needs to create a new `Bump`, it will be constructed by calling <code>Bump::[with_capacity]\(layout)</code>.
243    ///
244    /// [with_capacity]: Bump::with_capacity
245    ///
246    /// # Panics
247    /// Panics if the allocation fails.
248    #[must_use]
249    #[inline(always)]
250    #[cfg(feature = "panic-on-alloc")]
251    pub fn get_with_capacity(&self, layout: Layout) -> BumpPoolGuard<'_, A, S> {
252        panic_on_error(self.generic_get_with_capacity(layout))
253    }
254
255    /// Borrows a bump allocator from the pool.
256    /// With this `BumpPoolGuard` you can make allocations that live for as long as the pool lives.
257    ///
258    ///  If this needs to create a new `Bump`, it will be constructed by calling <code>Bump::[try_with_capacity]\(layout)</code>.
259    ///
260    /// [try_with_capacity]: Bump::try_with_capacity
261    ///
262    /// # Errors
263    /// Errors if the allocation fails.
264    #[inline(always)]
265    pub fn try_get_with_capacity(&self, layout: Layout) -> Result<BumpPoolGuard<'_, A, S>, AllocError> {
266        self.generic_get_with_capacity(layout)
267    }
268
269    pub(crate) fn generic_get_with_capacity<E: ErrorBehavior>(&self, layout: Layout) -> Result<BumpPoolGuard<'_, A, S>, E> {
270        let bump = match self.lock().pop() {
271            Some(bump) => bump,
272            None => Bump::generic_with_capacity_in(layout, self.allocator.clone())?,
273        };
274
275        Ok(BumpPoolGuard {
276            pool: self,
277            bump: ManuallyDrop::new(bump),
278        })
279    }
280}
281
282macro_rules! make_pool_guard {
283    ($($allocator_parameter:tt)*) => {
284
285        /// This is a wrapper around [`Bump`] that mutably derefs to a [`BumpScope`] and returns its [`Bump`] back to the [`BumpPool`] on drop.
286        #[derive(Debug)]
287        pub struct BumpPoolGuard<'a, $($allocator_parameter)*, S = BumpSettings>
288        where
289            A: Allocator,
290            S: BumpAllocatorSettings,
291        {
292            bump: ManuallyDrop<Bump<A, S>>,
293            pool: &'a BumpPool<A, S>,
294        }
295    };
296}
297
298maybe_default_allocator!(make_pool_guard);
299
300impl<'a, A, S> BumpPoolGuard<'a, A, S>
301where
302    A: Allocator,
303    S: BumpAllocatorSettings,
304{
305    /// The [`BumpPool`], this [`BumpPoolGuard`] was created from.
306    pub fn pool(&self) -> &'a BumpPool<A, S> {
307        self.pool
308    }
309}
310
311impl<'a, A, S> Deref for BumpPoolGuard<'a, A, S>
312where
313    A: Allocator,
314    S: BumpAllocatorSettings,
315{
316    type Target = BumpScope<'a, A, S>;
317
318    #[inline(always)]
319    fn deref(&self) -> &Self::Target {
320        unsafe { transmute_lifetime(self.bump.as_scope()) }
321    }
322}
323
324impl<A, S> DerefMut for BumpPoolGuard<'_, A, S>
325where
326    A: Allocator,
327    S: BumpAllocatorSettings,
328{
329    #[inline(always)]
330    fn deref_mut(&mut self) -> &mut Self::Target {
331        unsafe { transmute_lifetime_mut(self.bump.as_mut_scope()) }
332    }
333}
334
335impl<A, S> Drop for BumpPoolGuard<'_, A, S>
336where
337    A: Allocator,
338    S: BumpAllocatorSettings,
339{
340    fn drop(&mut self) {
341        let bump = unsafe { ManuallyDrop::take(&mut self.bump) };
342        self.pool.lock().push(bump);
343    }
344}
345
346// This exists as a "safer" transmute that only transmutes the `'a` lifetime parameter.
347#[expect(clippy::elidable_lifetime_names)]
348unsafe fn transmute_lifetime<'from, 'to, 'b, A, S>(scope: &'b BumpScope<'from, A, S>) -> &'b BumpScope<'to, A, S>
349where
350    S: BumpAllocatorSettings,
351{
352    unsafe { mem::transmute(scope) }
353}
354
355// This exists as a "safer" transmute that only transmutes the `'a` lifetime parameter.
356#[expect(clippy::elidable_lifetime_names)]
357unsafe fn transmute_lifetime_mut<'from, 'to, 'b, A, S>(scope: &'b mut BumpScope<'from, A, S>) -> &'b mut BumpScope<'to, A, S>
358where
359    S: BumpAllocatorSettings,
360{
361    unsafe { mem::transmute(scope) }
362}