Skip to main content

alloc_once/
lib.rs

1//! `alloc_once!` — allocate several different-typed values in a single bump
2//! allocation and get back a guard that owns the arena.
3//!
4//! This crate is a thin facade over [`bumpalo`]: it re-exports the [`alloc_once`]
5//! procedural macro and provides the [`AllocOnce`] guard the macro expands into.
6//! Callers never name bumpalo themselves.
7//!
8//! ```
9//! use alloc_once::alloc_once;
10//!
11//! let mut guard = alloc_once!(1i32, [10u8, 20, 30], "hello");
12//! let (n, bytes, s) = &mut *guard;   // independent &mut to each field
13//! *n += 41;
14//! bytes[0] = 99;
15//! assert_eq!(*n, 42);
16//! assert_eq!(bytes[0], 99);
17//! assert_eq!(*s, "hello");
18//! // `guard` drops here: it runs the values' destructors, then frees the arena.
19//! ```
20
21// The `alloc_once!` expansion refers to this crate by its absolute path
22// (`::alloc_once::...`). That resolves fine in downstream crates, but inside this
23// crate itself the name isn't in the extern prelude, so alias the crate to itself
24// to make the macro usable in our own doctests and unit tests.
25extern crate self as alloc_once;
26
27pub use alloc_once_macros::alloc_once;
28
29/// Implementation details referenced by the [`alloc_once!`] expansion.
30///
31/// Not part of the public API — do not use directly.
32#[doc(hidden)]
33pub mod __private {
34    pub use bumpalo::Bump;
35}
36
37/// Owns a bump arena and the single value allocated inside it.
38///
39/// Access the value through `Deref`/`DerefMut` (`&*guard` / `&mut *guard`). When
40/// the guard is dropped it runs the value's destructor and then frees the arena —
41/// unlike a raw `bumpalo::Bump::alloc`, whose values are never dropped.
42///
43/// Construct one with the [`alloc_once!`] macro rather than by hand.
44pub struct AllocOnce<T> {
45    // `ptr` points into a heap chunk owned by `arena`. A `Bump`'s chunks live on
46    // the heap, so moving the `Bump` value into this struct does not move the
47    // chunk memory, and the pointer stays valid for as long as the guard lives.
48    //
49    // Field order matters: `ptr` is listed first so that, combined with the
50    // manual `Drop` below, the value is dropped before `arena` frees its memory.
51    ptr: *mut T,
52    // Held only to keep the backing memory alive and to free it on drop; never
53    // read directly, hence the allow.
54    #[allow(dead_code)]
55    arena: bumpalo::Bump,
56}
57
58impl<T> AllocOnce<T> {
59    /// Build a guard from an arena and a pointer to a value allocated inside it.
60    ///
61    /// # Safety
62    ///
63    /// `ptr` must point to a valid, initialized `T` that was allocated in
64    /// `arena` and is uniquely owned by the returned guard. The [`alloc_once!`]
65    /// macro is the only intended caller.
66    #[doc(hidden)]
67    pub unsafe fn __from_parts(arena: bumpalo::Bump, ptr: *mut T) -> Self {
68        Self { ptr, arena }
69    }
70}
71
72impl<T> core::ops::Deref for AllocOnce<T> {
73    type Target = T;
74
75    fn deref(&self) -> &T {
76        // SAFETY: `ptr` is valid for the guard's lifetime and uniquely owned.
77        unsafe { &*self.ptr }
78    }
79}
80
81impl<T> core::ops::DerefMut for AllocOnce<T> {
82    fn deref_mut(&mut self) -> &mut T {
83        // SAFETY: `&mut self` guarantees unique access to the owned value.
84        unsafe { &mut *self.ptr }
85    }
86}
87
88impl<T> Drop for AllocOnce<T> {
89    fn drop(&mut self) {
90        // SAFETY: the guard is the sole owner of the value, so dropping it here
91        // is sound and cannot double-drop. `arena` is dropped right after this
92        // body runs, freeing the chunk the value lived in.
93        unsafe { core::ptr::drop_in_place(self.ptr) }
94    }
95}
96
97#[cfg(test)]
98mod tests {
99    use crate::alloc_once;
100    use std::cell::Cell;
101    use std::rc::Rc;
102
103    #[test]
104    fn single_value() {
105        let mut g = alloc_once!(1u8);
106        let (only,) = &mut *g;
107        *only += 1;
108        assert_eq!(*only, 2);
109    }
110
111    #[test]
112    fn many_different_types() {
113        let mut g = alloc_once!(1i32, [10u8, 20, 30], "hello");
114        let (n, bytes, s) = &mut *g;
115        *n += 41;
116        bytes[0] = 99;
117        assert_eq!((*n, bytes[0], *s), (42, 99, "hello"));
118    }
119
120    #[test]
121    fn drops_value_when_guard_drops() {
122        // A value whose Drop flips a shared flag, proving the guard runs
123        // destructors (a raw bump allocation would not).
124        struct Flag(Rc<Cell<bool>>);
125        impl Drop for Flag {
126            fn drop(&mut self) {
127                self.0.set(true);
128            }
129        }
130
131        let dropped = Rc::new(Cell::new(false));
132        let guard = alloc_once!(Flag(Rc::clone(&dropped)));
133        assert!(!dropped.get());
134        drop(guard);
135        assert!(dropped.get());
136    }
137}