alloc-once 0.1.0-rc.1

Allocate several different-typed values in one bump allocation, returned as a self-owning guard.
Documentation
//! `alloc_once!` — allocate several different-typed values in a single bump
//! allocation and get back a guard that owns the arena.
//!
//! This crate is a thin facade over [`bumpalo`]: it re-exports the [`alloc_once`]
//! procedural macro and provides the [`AllocOnce`] guard the macro expands into.
//! Callers never name bumpalo themselves.
//!
//! ```
//! use alloc_once::alloc_once;
//!
//! let mut guard = alloc_once!(1i32, [10u8, 20, 30], "hello");
//! let (n, bytes, s) = &mut *guard;   // independent &mut to each field
//! *n += 41;
//! bytes[0] = 99;
//! assert_eq!(*n, 42);
//! assert_eq!(bytes[0], 99);
//! assert_eq!(*s, "hello");
//! // `guard` drops here: it runs the values' destructors, then frees the arena.
//! ```

// The `alloc_once!` expansion refers to this crate by its absolute path
// (`::alloc_once::...`). That resolves fine in downstream crates, but inside this
// crate itself the name isn't in the extern prelude, so alias the crate to itself
// to make the macro usable in our own doctests and unit tests.
extern crate self as alloc_once;

pub use alloc_once_macros::alloc_once;

/// Implementation details referenced by the [`alloc_once!`] expansion.
///
/// Not part of the public API — do not use directly.
#[doc(hidden)]
pub mod __private {
    pub use bumpalo::Bump;
}

/// Owns a bump arena and the single value allocated inside it.
///
/// Access the value through `Deref`/`DerefMut` (`&*guard` / `&mut *guard`). When
/// the guard is dropped it runs the value's destructor and then frees the arena —
/// unlike a raw `bumpalo::Bump::alloc`, whose values are never dropped.
///
/// Construct one with the [`alloc_once!`] macro rather than by hand.
pub struct AllocOnce<T> {
    // `ptr` points into a heap chunk owned by `arena`. A `Bump`'s chunks live on
    // the heap, so moving the `Bump` value into this struct does not move the
    // chunk memory, and the pointer stays valid for as long as the guard lives.
    //
    // Field order matters: `ptr` is listed first so that, combined with the
    // manual `Drop` below, the value is dropped before `arena` frees its memory.
    ptr: *mut T,
    // Held only to keep the backing memory alive and to free it on drop; never
    // read directly, hence the allow.
    #[allow(dead_code)]
    arena: bumpalo::Bump,
}

impl<T> AllocOnce<T> {
    /// Build a guard from an arena and a pointer to a value allocated inside it.
    ///
    /// # Safety
    ///
    /// `ptr` must point to a valid, initialized `T` that was allocated in
    /// `arena` and is uniquely owned by the returned guard. The [`alloc_once!`]
    /// macro is the only intended caller.
    #[doc(hidden)]
    pub unsafe fn __from_parts(arena: bumpalo::Bump, ptr: *mut T) -> Self {
        Self { ptr, arena }
    }
}

impl<T> core::ops::Deref for AllocOnce<T> {
    type Target = T;

    fn deref(&self) -> &T {
        // SAFETY: `ptr` is valid for the guard's lifetime and uniquely owned.
        unsafe { &*self.ptr }
    }
}

impl<T> core::ops::DerefMut for AllocOnce<T> {
    fn deref_mut(&mut self) -> &mut T {
        // SAFETY: `&mut self` guarantees unique access to the owned value.
        unsafe { &mut *self.ptr }
    }
}

impl<T> Drop for AllocOnce<T> {
    fn drop(&mut self) {
        // SAFETY: the guard is the sole owner of the value, so dropping it here
        // is sound and cannot double-drop. `arena` is dropped right after this
        // body runs, freeing the chunk the value lived in.
        unsafe { core::ptr::drop_in_place(self.ptr) }
    }
}

#[cfg(test)]
mod tests {
    use crate::alloc_once;
    use std::cell::Cell;
    use std::rc::Rc;

    #[test]
    fn single_value() {
        let mut g = alloc_once!(1u8);
        let (only,) = &mut *g;
        *only += 1;
        assert_eq!(*only, 2);
    }

    #[test]
    fn many_different_types() {
        let mut g = alloc_once!(1i32, [10u8, 20, 30], "hello");
        let (n, bytes, s) = &mut *g;
        *n += 41;
        bytes[0] = 99;
        assert_eq!((*n, bytes[0], *s), (42, 99, "hello"));
    }

    #[test]
    fn drops_value_when_guard_drops() {
        // A value whose Drop flips a shared flag, proving the guard runs
        // destructors (a raw bump allocation would not).
        struct Flag(Rc<Cell<bool>>);
        impl Drop for Flag {
            fn drop(&mut self) {
                self.0.set(true);
            }
        }

        let dropped = Rc::new(Cell::new(false));
        let guard = alloc_once!(Flag(Rc::clone(&dropped)));
        assert!(!dropped.get());
        drop(guard);
        assert!(dropped.get());
    }
}