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}