dynamic_arena/
lib.rs

1//! Implements dynamically typed arenas, where any type of item can be allocated.
2#![deny(missing_docs)]
3use std::alloc::Layout;
4use std::cell::RefCell;
5use std::marker::PhantomData;
6use std::mem;
7use std::os::raw::c_void;
8use std::ptr::{self, NonNull};
9
10use bumpalo::Bump;
11
12/// Marker trait that indicates whether or a `DynamicArena` may be sent across threads
13pub trait SendAbility: Sized {
14    /// Create an arena corresponding to this type of thread-safety
15    fn create_arena<'a>() -> DynamicArena<'a, Self>;
16}
17/// Marker type that indicates you expect everything in the `DynamicArena` to be `Send`
18///
19/// Although this prevents you from allocating non-`Send` types in the arena,
20/// it allows you to `Send` the dynamic itself arena across threads.
21/// We can't safely implement `Send` for `DynamicArena` without this bound,
22/// since you could otherwise place a `Rc` in the arena, send it across threads,
23/// and then proceed to drop the arena and mutate the reference count.
24pub struct Sendable {
25    _marker: (),
26}
27impl SendAbility for Sendable {
28    #[inline]
29    fn create_arena<'a>() -> DynamicArena<'a, Self> {
30        DynamicArena::new_send()
31    }
32}
33/// Marker type that indiates everything in the `DynamicArena` isn't nesicarrily `Send`.
34///
35/// This prevents you from `Send`ing the arena itself across threads,
36/// as described in the `Sendable` docs.
37pub struct NonSend {
38    _marker: std::rc::Rc<()>,
39}
40impl SendAbility for NonSend {
41    #[inline]
42    fn create_arena<'a>() -> DynamicArena<'a, Self> {
43        DynamicArena::new_bounded()
44    }
45}
46
47struct DynamicArenaItem {
48    drop: unsafe fn(*mut c_void),
49    value: *mut c_void,
50}
51impl Drop for DynamicArenaItem {
52    #[inline]
53    fn drop(&mut self) {
54        unsafe { (self.drop)(self.value) }
55    }
56}
57unsafe impl Send for DynamicArenaItem {}
58
59/// An alias for an arena allocator which requires that everything is `Send + 'a`.
60pub type DynamicSendArena<'a> = DynamicArena<'a, Sendable>;
61
62/// An arena allocator where any type of object can be allocated.
63///
64/// Unlike typed arenas, any `Sized` object can be allocated here and they
65/// don't all have to have the same statically known type in advance.
66/// Usually you don't have to worry about the arena's lifetime,
67/// since it should be static by default (see).
68///
69/// ## Performance
70/// Although this is _slightly_ slower than a `typed_arena::Arena`,
71/// it can be much more memory and time efficient than having a ton of seperate typed arenas.
72/// The only point where dynamic dispatch actually gets involved is when the arena is dropped,
73/// since we have to dynamically dispatch the drop functions instead of statically dispatching them.
74///
75/// ## Safety
76/// In order to prevent use after free in a `DynamicArena`, all pointers in the allocated items
77/// need to be valid for the lifetime `'a` to ensure all references outlive the arena itself.
78/// Unfortunately, this statically prevents all self referential structs with `alloc`,
79/// since they can't be known ahead of time to be safe and outlive the arena itself,
80/// and we can't perform [dropchk](https://doc.rust-lang.org/nightly/nomicon/dropck.html)
81/// on a dynamically typed arena (only statically typed ones).
82///
83/// The alternatives to this are `alloc_unchecked`, which bypasses the lifetime and safety,
84/// and `alloc_copy`, which bypasses the lifetime by ensuring `T: Copy`.
85/// This is safe, since a copyable item can never have a custom drop,
86/// and the drop function could never possibly trigger use after free.
87///
88/// This means you can use self-referential structs with a `DynamicArena` as long as they implement `Copy`.
89/// One way to make your types implement copy and support self-refrential structs,
90/// is by replacing owned objects with their borrowed counterparts and then allocating them in the arena.
91/// For example
92/// ````
93/// struct OwnedSelfReferential<'a> {
94///    next: Option<&'a OwnedSelfReferential<'a>>,
95///    text: String,
96///    array: Vec<u32>
97/// }
98/// ````
99/// can't be used with either `alloc` (since it's self referential),
100/// nor `alloc_copy` (since `String` and `Vec` need to be dropped).
101/// However by replacing `String` with `&'a str` and `&'a [u32]`,
102/// we can make the structure `Copy` and enable use with `alloc_copy`.
103///
104/// Then, when someone needs to arena-allocate the struct they can use
105/// the same arena to allocate the `String` and `Vec<u32>` first,
106/// before they proceed to allocate the copyable struct.
107pub struct DynamicArena<'a, S = NonSend> {
108    /// The underlying arena, where we request that they allocate arbitrary bytes.
109    handle: Bump,
110    /// The list of untyped values we've allocated in the arena,
111    /// and whose drop functions need to be invoked.
112    ///
113    /// The drop functions are dynamically dispatched,
114    /// and each item could invoke completely different code for completely different types.
115    /// This is only needed for types that need to be dropped (as determined by `mem::needs_drop`),
116    /// and types that need need to be dropped don't need to be added.
117    items: RefCell<Vec<DynamicArenaItem>>,
118    /// This is the magic `PhantomData` combination to have proper lifetime invariance.
119    ///
120    /// Otherwise the lifetime would be 'variant',
121    /// and borrow checking wouldn't properly enforce the bound for `alloc`.
122    /// This is enforced by the `compile-fail/invalid-drop-counted.rs`
123    marker: PhantomData<*mut &'a ()>,
124    send: PhantomData<S>,
125}
126impl DynamicArena<'static, NonSend> {
127    /// Create a new dynamic arena whose allocated items must outlive the `'static` lifetime,
128    /// and whose items aren't required to be `Send`.
129    ///
130    /// Usually this is what you want,
131    /// since it's only a bound for the allocated items.
132    #[inline]
133    pub fn new() -> Self {
134        DynamicArena::new_bounded()
135    }
136}
137impl<'a, S> DynamicArena<'a, S> {
138    /// Create an arena with pre-allocated capacity for the specified number of items
139    /// and bytes.
140    ///
141    /// NOTE: The "item" capacity excludes `Copy` references that
142    /// don't need to be dropped.
143    pub fn with_capacity(item_capacity: usize, byte_capacity: usize) -> Self {
144        DynamicArena {
145            handle: Bump::with_capacity(byte_capacity),
146            items: RefCell::new(Vec::with_capacity(item_capacity)),
147            marker: PhantomData,
148            send: PhantomData,
149        }
150    }
151    /// Allocate the specified value in this arena,
152    /// returning a reference which will be valid for the lifetime of the entire arena.
153    ///
154    /// The bound on the item requires that `T: Copy`
155    /// to ensure there's no drop function that needs to be invoked.
156    #[inline]
157    #[allow(clippy::mut_from_ref)]
158    pub fn alloc_copy<T: Copy + Send>(&self, value: T) -> &mut T {
159        unsafe { self.alloc_unchecked(value) }
160    }
161    /// Allocate the specified value in this arena,
162    /// without calling its `Drop` function.
163    ///
164    /// Since this doesn't call the 'Drop' impleentation,
165    /// this function leaks the underlying memory.
166    ///
167    /// ## Safety
168    /// Technically, this function is safe to use.
169    /// However, it leaks memory unconditionally (without calling Drop).
170    #[inline]
171    #[allow(clippy::mut_from_ref)]
172    pub unsafe fn alloc_unchecked<T>(&self, value: T) -> &mut T {
173        let ptr = self.alloc_layout(Layout::new::<T>()).as_ptr().cast::<T>();
174        ptr.write(value);
175        &mut *ptr
176    }
177    /// Allocate space for an object with the specified layout
178    ///
179    /// The returned pointer points at uninitialized memory
180    ///
181    /// ## Safety
182    /// Technically, only the use of the memory is unsafe.
183    ///
184    /// It would theoretically be possible to mark this function safe,
185    /// just like [Bump::alloc_layout].
186    #[inline]
187    pub unsafe fn alloc_layout(&self, layout: Layout) -> NonNull<u8> {
188        self.handle.alloc_layout(layout)
189    }
190    /// Dynamically drop the specified value,
191    /// invoking the drop function when the arena is dropped.
192    ///
193    /// ## Safety
194    /// This assumes it's safe to drop the value at the same time the arena is dropped.
195    /// Not only are you assuming that [ptr::drop_in_place] would be safe,
196    /// you're also assuming that the drop function won't reference any dangling pointers,
197    /// and that [dropchk](https://doc.rust-lang.org/nomicon/dropck.html) would be successful.
198    ///
199    /// Normally these invariants are statically checked by the `alloc` method,
200    /// which ensures that the memory is owned and all pointers
201    /// would be valid for the lifetime of the entire arena.
202    #[inline]
203    pub unsafe fn dynamic_drop<T>(&self, value: *mut T) {
204        if mem::needs_drop::<T>() {
205            self.items.borrow_mut().push(DynamicArenaItem {
206                drop: mem::transmute::<unsafe fn(*mut T), unsafe fn(*mut c_void)>(
207                    ptr::drop_in_place::<T>,
208                ),
209                value: value as *mut c_void,
210            })
211        }
212    }
213    /// Retrieve the underlying [bump allocator](bumpalo::Bump) for this arena
214    #[inline]
215    pub fn as_bumpalo(&self) -> &'_ bumpalo::Bump {
216        &self.handle
217    }
218}
219impl<'a> DynamicArena<'a, Sendable> {
220    /// Create a new empty arena, bounded by the inferred lifetime for this type `'a`
221    ///
222    /// Since this arena has been marked `Sendable`,
223    /// all items in the arena need to implement `Send`.
224    pub fn new_send() -> Self {
225        DynamicArena {
226            handle: Bump::new(),
227            items: RefCell::new(Vec::new()),
228            marker: PhantomData,
229            send: PhantomData,
230        }
231    }
232    /// Allocate the specified value in this arena,
233    /// returning a reference which will be valid for the lifetime of the entire arena.
234    ///
235    /// The bound on this item requires that `T: 'a`
236    /// to ensure the drop function is safe to invoke.
237    /// Additionally, since the arena is `Sendable`,
238    /// the bound on the item also requires that `T: Send`.
239    #[inline]
240    #[allow(clippy::mut_from_ref)]
241    pub fn alloc<T: Send + 'a>(&self, value: T) -> &mut T {
242        unsafe {
243            let target = self.alloc_unchecked(value);
244            self.dynamic_drop(target);
245            target
246        }
247    }
248}
249impl<'a> DynamicArena<'a, NonSend> {
250    /// Create a new empty arena, bounded by the inferred lifetime for this type `'a`
251    ///
252    /// Since this arena has been marked `NonSend`,
253    /// the items in the arena don't necessarily need to implement `Send`.
254    pub fn new_bounded() -> Self {
255        DynamicArena {
256            handle: Bump::new(),
257            items: RefCell::new(Vec::new()),
258            marker: PhantomData,
259            send: PhantomData,
260        }
261    }
262    /// Allocate the specified value in this arena,
263    /// returning a reference which will be valid for the lifetime of the entire arena.
264    ///
265    /// The bound on this item requires that `T: 'a`
266    /// to ensure the drop function is safe to invoke.
267    #[inline]
268    #[allow(clippy::mut_from_ref)]
269    pub fn alloc<T: 'a>(&self, value: T) -> &mut T {
270        unsafe {
271            let target = self.alloc_unchecked(value);
272            self.dynamic_drop(target);
273            target
274        }
275    }
276}
277impl<'a, S: SendAbility> Default for DynamicArena<'a, S> {
278    #[inline]
279    fn default() -> Self {
280        S::create_arena()
281    }
282}
283unsafe impl<'a, S: SendAbility + Send> Send for DynamicArena<'a, S> {}
284impl<'a, S> Drop for DynamicArena<'a, S> {
285    #[inline]
286    fn drop(&mut self) {
287        // Items must be dropped before the arena
288        self.items.get_mut().clear();
289    }
290}
291
292#[cfg(test)]
293mod test {
294    use super::*;
295    use std::cell::Cell;
296
297    const EXPECTED_DROP_COUNT: u32 = 4787;
298    const EXPECTED_DEPTHS: &[u32] = &[5, 27, 43];
299
300    struct DropCounted<'a>(&'a Cell<u32>);
301    impl<'a> Drop for DropCounted<'a> {
302        fn drop(&mut self) {
303            let old_count = self.0.get();
304            self.0.set(old_count + 1);
305        }
306    }
307    #[derive(Copy, Clone)]
308    pub struct SelfReferential<'a>(u32, Option<&'a SelfReferential<'a>>);
309    impl<'a> SelfReferential<'a> {
310        #[inline]
311        pub fn with_depth(arena: &'a DynamicArena, depth: u32) -> &'a Self {
312            arena.alloc_copy(match depth {
313                0 => SelfReferential(depth, None),
314                _ => SelfReferential(depth, Some(SelfReferential::with_depth(arena, depth - 1))),
315            })
316        }
317        #[inline]
318        pub fn depth(&self) -> u32 {
319            match self.1 {
320                Some(inner) => inner.depth() + 1,
321                None => 0,
322            }
323        }
324    }
325    #[test]
326    fn test_send() {
327        let arena = DynamicArena::new_send();
328        verify_copyable(do_copyable(&arena));
329        ::std::thread::spawn(move || {
330            verify_copyable(do_copyable(&arena));
331        });
332    }
333
334    #[test]
335    fn copyable() {
336        let arena = DynamicArena::new();
337        for _ in 0..5 {
338            verify_copyable(do_copyable(&arena));
339        }
340    }
341    #[test]
342    fn self_referential() {
343        let arena = DynamicArena::new();
344        for _ in 0..5 {
345            verify_self_referential(do_self_referential(&arena));
346        }
347    }
348    #[test]
349    fn drop_counted() {
350        let cell = Box::new(Cell::new(0));
351        let arena = DynamicArena::new_bounded();
352        {
353            do_drop_counted(&arena, &cell);
354            assert_eq!(cell.get(), 0);
355        }
356        drop(arena);
357        assert_eq!(cell.get(), EXPECTED_DROP_COUNT);
358    }
359    #[test]
360    fn mixed() {
361        let cell = Cell::new(0);
362        let arena = DynamicArena::new_bounded();
363        {
364            do_drop_counted(&arena, &cell);
365            for _ in 0..5 {
366                verify_copyable(do_copyable(&arena));
367                verify_self_referential(do_self_referential(&arena));
368            }
369            assert_eq!(cell.get(), 0);
370        }
371        drop(arena);
372        assert_eq!(cell.get(), EXPECTED_DROP_COUNT);
373    }
374    fn do_copyable<'a, S>(arena: &'a DynamicArena<S>) -> Vec<&'a u32> {
375        let mut results = Vec::new();
376        for i in 0..10 {
377            results.push(&*arena.alloc_copy(i * 3));
378        }
379        results
380    }
381    fn verify_copyable(results: Vec<&u32>) {
382        for (actual, expected) in results.iter().zip(0..10) {
383            assert_eq!(**actual, expected * 3);
384        }
385    }
386    fn do_drop_counted<'a, 'd: 'a>(arena: &'a DynamicArena<'d>, counter: &'d Cell<u32>) {
387        for _ in 0..EXPECTED_DROP_COUNT {
388            arena.alloc(DropCounted(counter));
389        }
390    }
391    fn do_self_referential<'a>(arena: &'a DynamicArena) -> Vec<&'a SelfReferential<'a>> {
392        let mut result = Vec::new();
393        for &depth in EXPECTED_DEPTHS {
394            result.push(SelfReferential::with_depth(arena, depth));
395        }
396        result
397    }
398    fn verify_self_referential<'a>(results: Vec<&'a SelfReferential<'a>>) {
399        for (&actual, &depth) in results.iter().zip(EXPECTED_DEPTHS.iter()) {
400            assert_eq!(actual.depth(), depth);
401        }
402    }
403}