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