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}