alloc_once_macros/lib.rs
1//! Procedural macro backing `alloc_once!`.
2//!
3//! `alloc_once!(v1, v2, ...)` packs the values into a single tuple, allocates
4//! that tuple once inside a freshly created bump arena, and returns an
5//! `alloc_once::AllocOnce` guard that owns the arena. All bumpalo details are
6//! hidden: the expansion only names paths under `::alloc_once`, so callers do
7//! not need a bumpalo dependency of their own.
8
9use proc_macro::TokenStream;
10use quote::quote;
11use syn::parse::{Parse, ParseStream};
12use syn::punctuated::Punctuated;
13use syn::{Expr, Token};
14
15/// The parsed macro input: a comma-separated list of value expressions.
16struct Values {
17 values: Punctuated<Expr, Token![,]>,
18}
19
20impl Parse for Values {
21 fn parse(input: ParseStream) -> syn::Result<Self> {
22 let values = Punctuated::parse_terminated(input)?;
23 if values.is_empty() {
24 return Err(syn::Error::new(
25 input.span(),
26 "alloc_once! needs at least one value to allocate",
27 ));
28 }
29 Ok(Values { values })
30 }
31}
32
33/// Allocate several (possibly different-typed) values in a single bump
34/// allocation and return a guard that owns the backing arena.
35///
36/// ```ignore
37/// let mut guard = alloc_once!(1i32, "hi", [0u8; 4]);
38/// let (a, b, c) = &mut *guard; // a: &mut i32, b: &mut &str, c: &mut [u8; 4]
39/// ```
40#[proc_macro]
41pub fn alloc_once(input: TokenStream) -> TokenStream {
42 let Values { values } = syn::parse_macro_input!(input as Values);
43 let values = values.iter();
44
45 let expanded = quote! {
46 {
47 // Build the bundle first. The trailing comma keeps the single-value
48 // case a 1-tuple `(v,)`.
49 let __alloc_once_value = ( #(#values,)* );
50 // Size the arena to hold exactly this bundle and nothing more. For a
51 // sized tuple this is a compile-time constant, so the arena allocates
52 // a single chunk of precisely the bytes needed.
53 let __alloc_once_cap = ::core::mem::size_of_val(&__alloc_once_value);
54 let __alloc_once_arena =
55 ::alloc_once::__private::Bump::with_capacity(__alloc_once_cap);
56 let __alloc_once_ptr: *mut _ = __alloc_once_arena.alloc(__alloc_once_value);
57 unsafe { ::alloc_once::AllocOnce::__from_parts(__alloc_once_arena, __alloc_once_ptr) }
58 }
59 };
60
61 expanded.into()
62}