Skip to main content

oxc_allocator/
boxed.rs

1//! Arena Box.
2//!
3//! Originally based on [jsparagus](https://github.com/mozilla-spidermonkey/jsparagus/blob/24004745a8ed4939fc0dc7332bfd1268ac52285f/crates/ast/src/arena.rs)
4
5use std::{
6    self,
7    fmt::{self, Debug, Display, Formatter},
8    hash::{Hash, Hasher},
9    marker::PhantomData,
10    mem,
11    ops::{Deref, DerefMut},
12    ptr::{self, NonNull},
13};
14
15#[cfg(feature = "serialize")]
16use oxc_estree::{ESTree, Serializer as ESTreeSerializer};
17#[cfg(feature = "serialize")]
18use serde::{Serialize, Serializer as SerdeSerializer};
19
20use crate::Allocator;
21
22/// A `Box` without [`Drop`], which stores its data in the arena allocator.
23///
24/// # No `Drop`s
25///
26/// Objects allocated into Oxc memory arenas are never [`Dropped`](Drop). Memory is released in bulk
27/// when the allocator is dropped, without dropping the individual objects in the arena.
28///
29/// Therefore, it would produce a memory leak if you allocated [`Drop`] types into the arena
30/// which own memory allocations outside the arena.
31///
32/// Static checks make this impossible to do. [`Box::new_in`] will refuse to compile if called
33/// with a [`Drop`] type.
34#[repr(transparent)]
35pub struct Box<'alloc, T: ?Sized>(NonNull<T>, PhantomData<(&'alloc (), T)>);
36
37impl<T: ?Sized> Box<'_, T> {
38    /// Const assertion that `T` is not `Drop`.
39    /// Must be referenced in all methods which create a `Box`.
40    const ASSERT_T_IS_NOT_DROP: () =
41        assert!(!std::mem::needs_drop::<T>(), "Cannot create a Box<T> where T is a Drop type");
42}
43
44impl<T> Box<'_, T> {
45    /// Put a `value` into a memory arena and get back a [`Box`] with ownership
46    /// to the allocation.
47    ///
48    /// # Examples
49    /// ```
50    /// use oxc_allocator::{Allocator, Box};
51    ///
52    /// let arena = Allocator::default();
53    /// let in_arena: Box<i32> = Box::new_in(5, &arena);
54    /// ```
55    //
56    // `#[inline(always)]` because this is a hot path and `Allocator::alloc` is a very small function.
57    // We always want it to be inlined.
58    #[expect(clippy::inline_always)]
59    #[inline(always)]
60    pub fn new_in(value: T, allocator: &Allocator) -> Self {
61        const { Self::ASSERT_T_IS_NOT_DROP };
62
63        Self(NonNull::from(allocator.alloc(value)), PhantomData)
64    }
65
66    /// Create a fake [`Box`] with a dangling pointer.
67    ///
68    /// # SAFETY
69    /// Safe to create, but must never be dereferenced, as does not point to a valid `T`.
70    /// Only purpose is for mocking types without allocating for const assertions.
71    pub const unsafe fn dangling() -> Self {
72        // SAFETY: None of `from_non_null`'s invariants are satisfied, but caller promises
73        // never to dereference the `Box`
74        unsafe { Self::from_non_null(ptr::NonNull::dangling()) }
75    }
76
77    /// Take ownership of the value stored in this [`Box`], consuming the box in
78    /// the process.
79    ///
80    /// # Examples
81    /// ```
82    /// use oxc_allocator::{Allocator, Box};
83    ///
84    /// let arena = Allocator::default();
85    ///
86    /// // Put `5` into the arena and on the heap.
87    /// let boxed: Box<i32> = Box::new_in(5, &arena);
88    /// // Move it back to the stack. `boxed` has been consumed.
89    /// let i = boxed.unbox();
90    ///
91    /// assert_eq!(i, 5);
92    /// ```
93    #[inline]
94    pub fn unbox(self) -> T {
95        // SAFETY:
96        // This pointer read is safe because the reference `self.0` is
97        // guaranteed to be unique - not just now, but we're guaranteed it's not
98        // borrowed from some other reference. This in turn is because we never
99        // construct a `Box` with a borrowed reference, only with a fresh
100        // one just allocated from an `Arena`.
101        unsafe { ptr::read(self.0.as_ptr()) }
102    }
103}
104
105impl<T: ?Sized> Box<'_, T> {
106    /// Get a [`NonNull`] pointer pointing to the [`Box`]'s contents.
107    ///
108    /// The pointer is not valid for writes.
109    ///
110    /// The caller must ensure that the `Box` outlives the pointer this
111    /// function returns, or else it will end up dangling.
112    ///
113    /// # Example
114    ///
115    /// ```
116    /// use oxc_allocator::{Allocator, Box};
117    ///
118    /// let allocator = Allocator::new();
119    /// let boxed = Box::new_in(123_u64, &allocator);
120    /// let ptr = Box::as_non_null(&boxed);
121    /// ```
122    //
123    // `#[inline(always)]` because this is a no-op
124    #[expect(clippy::inline_always)]
125    #[inline(always)]
126    pub fn as_non_null(boxed: &Self) -> NonNull<T> {
127        boxed.0
128    }
129
130    /// Consume a [`Box`] and return a [`NonNull`] pointer to its contents.
131    //
132    // `#[inline(always)]` because this is a no-op
133    #[expect(clippy::inline_always, clippy::needless_pass_by_value)]
134    #[inline(always)]
135    pub fn into_non_null(boxed: Self) -> NonNull<T> {
136        boxed.0
137    }
138
139    /// Create a [`Box`] from a [`NonNull`] pointer.
140    ///
141    /// # SAFETY
142    ///
143    /// * Pointer must point to a valid `T`.
144    /// * Pointer must point to within an `Allocator`.
145    /// * Caller must ensure that the pointer is valid for the lifetime of the `Box`.
146    pub const unsafe fn from_non_null(ptr: NonNull<T>) -> Self {
147        const { Self::ASSERT_T_IS_NOT_DROP };
148
149        Self(ptr, PhantomData)
150    }
151}
152
153impl<T> Box<'static, [T]> {
154    /// Create a new empty `Box<[T]>`.
155    ///
156    /// This method does not allocate. The returned boxed slice is represented by a dangling,
157    /// correctly-aligned pointer with length 0, similar to how `Vec::new_in` produces an empty vector.
158    #[inline]
159    pub fn new_empty_boxed_slice() -> Self {
160        const { Self::ASSERT_T_IS_NOT_DROP };
161
162        // `NonNull::<T>::dangling()` yields a non-null, properly aligned pointer.
163        // We pair it with length 0 to construct a `NonNull<[T]>` representing an empty slice.
164        // Correct alignment is the only requirement for it to be sound to dereference this pointer
165        // to a slice, because the slice is empty.
166        // See: https://doc.rust-lang.org/std/slice/fn.from_raw_parts.html
167        let ptr = NonNull::dangling();
168        let slice_ptr = NonNull::slice_from_raw_parts(ptr, 0);
169
170        Self(slice_ptr, PhantomData)
171    }
172}
173
174impl<'a, T> Box<'a, [T]> {
175    /// Convert a boxed slice [`Box<[T]>`] into slice [`&'a [T]`].
176    ///
177    /// The returned slice has the same lifetime as the allocator.
178    //
179    // `#[inline(always)]` because this is a no-op. `Box<[T]>` and `&[T]` have the same layout.
180    #[expect(clippy::inline_always)]
181    #[inline(always)]
182    pub fn into_arena_slice(self) -> &'a [T] {
183        let r = self.as_ref();
184        // Extend lifetime of reference to lifetime of the allocator.
185        // SAFETY: `self` is consumed by this method, so there cannot be any mutable references to it.
186        // The reference lives until the allocator is dropped or reset (`'a` lifetime).
187        // Don't need `mem::forget(self)` here, because `Box` does not implement `Drop`.
188        unsafe { mem::transmute::<&[T], &'a [T]>(r) }
189    }
190
191    /// Convert a boxed slice [`Box<[T]>`] into mutable slice [`&'a mut [T]`].
192    ///
193    /// The returned slice has the same lifetime as the allocator.
194    //
195    // `#[inline(always)]` because this is a no-op. `Box<[T]>` and `&mut [T]` have the same layout.
196    #[expect(clippy::inline_always)]
197    #[inline(always)]
198    pub fn into_arena_slice_mut(mut self) -> &'a mut [T] {
199        let r = self.as_mut();
200        // Extend lifetime of reference to lifetime of the allocator.
201        // SAFETY: `self` is consumed by this method, so there cannot be any other references to it.
202        // The reference lives until the allocator is dropped or reset (`'a` lifetime).
203        // Don't need `mem::forget(self)` here, because `Box` does not implement `Drop`.
204        unsafe { mem::transmute::<&mut [T], &'a mut [T]>(r) }
205    }
206}
207
208impl<T: ?Sized> Deref for Box<'_, T> {
209    type Target = T;
210
211    #[inline]
212    fn deref(&self) -> &T {
213        // SAFETY: `self.0` is always a unique reference allocated from an `Arena` in `Box::new_in`,
214        // or an empty slice allocated from `Box::new_empty_boxed_slice`
215        unsafe { self.0.as_ref() }
216    }
217}
218
219impl<T: ?Sized> DerefMut for Box<'_, T> {
220    #[inline]
221    fn deref_mut(&mut self) -> &mut T {
222        // SAFETY: `self.0` is always a unique reference allocated from an `Arena` in `Box::new_in`,
223        // or an empty slice allocated from `Box::new_empty_boxed_slice`
224        unsafe { self.0.as_mut() }
225    }
226}
227
228impl<T: ?Sized> AsRef<T> for Box<'_, T> {
229    #[inline]
230    fn as_ref(&self) -> &T {
231        self
232    }
233}
234
235impl<T: ?Sized> AsMut<T> for Box<'_, T> {
236    #[inline]
237    fn as_mut(&mut self) -> &mut T {
238        self
239    }
240}
241
242impl<T: ?Sized + Display> Display for Box<'_, T> {
243    #[inline]
244    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
245        self.deref().fmt(f)
246    }
247}
248
249impl<T: ?Sized + Debug> Debug for Box<'_, T> {
250    #[inline]
251    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
252        self.deref().fmt(f)
253    }
254}
255
256// Unused right now.
257// impl<'alloc, T> PartialEq for Box<'alloc, T>
258// where
259// T: PartialEq<T> + ?Sized,
260// {
261// fn eq(&self, other: &Box<'alloc, T>) -> bool {
262// PartialEq::eq(&**self, &**other)
263// }
264// }
265
266#[cfg(feature = "serialize")]
267impl<T: Serialize> Serialize for Box<'_, T> {
268    fn serialize<S: SerdeSerializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
269        self.deref().serialize(serializer)
270    }
271}
272
273#[cfg(feature = "serialize")]
274impl<T: ESTree> ESTree for Box<'_, T> {
275    fn serialize<S: ESTreeSerializer>(&self, serializer: S) {
276        self.deref().serialize(serializer);
277    }
278}
279
280impl<T: Hash> Hash for Box<'_, T> {
281    #[inline]
282    fn hash<H: Hasher>(&self, state: &mut H) {
283        self.deref().hash(state);
284    }
285}
286
287#[cfg(test)]
288mod test {
289    use std::hash::{DefaultHasher, Hash, Hasher};
290
291    use crate::{Allocator, Vec};
292
293    use super::Box;
294
295    #[test]
296    fn box_deref_mut() {
297        let allocator = Allocator::default();
298        let mut b = Box::new_in("x", &allocator);
299        let b = &mut *b;
300        *b = allocator.alloc("v");
301        assert_eq!(*b, "v");
302    }
303
304    #[test]
305    fn new_empty_boxed_slice() {
306        let b = Box::<[u32]>::new_empty_boxed_slice();
307        assert!(b.is_empty());
308        assert_eq!(b.len(), 0);
309        assert_eq!(&*b, &[] as &[u32]);
310    }
311
312    #[test]
313    fn boxed_slice_into_arena_slice() {
314        let allocator = Allocator::default();
315        let v = Vec::from_iter_in([1, 2, 3], &allocator);
316        let b = v.into_boxed_slice();
317        let slice = b.into_arena_slice();
318        assert_eq!(slice, &[1, 2, 3]);
319    }
320
321    #[test]
322    fn boxed_slice_into_arena_slice_mut() {
323        let allocator = Allocator::default();
324        let v = Vec::from_iter_in([10, 20, 30], &allocator);
325        let b = v.into_boxed_slice();
326        let slice = b.into_arena_slice_mut();
327        slice[1] = 99;
328        assert_eq!(slice, &[10, 99, 30]);
329    }
330
331    #[test]
332    fn box_debug() {
333        let allocator = Allocator::default();
334        let b = Box::new_in("x", &allocator);
335        let b = format!("{b:?}");
336        assert_eq!(b, "\"x\"");
337    }
338
339    #[test]
340    fn box_hash() {
341        fn hash(val: &impl Hash) -> u64 {
342            let mut hasher = DefaultHasher::default();
343            val.hash(&mut hasher);
344            hasher.finish()
345        }
346
347        let allocator = Allocator::default();
348        let a = Box::new_in("x", &allocator);
349        let b = Box::new_in("x", &allocator);
350
351        assert_eq!(hash(&a), hash(&b));
352    }
353
354    #[cfg(feature = "serialize")]
355    #[test]
356    fn box_serialize() {
357        let allocator = Allocator::default();
358        let b = Box::new_in("x", &allocator);
359        let s = serde_json::to_string(&b).unwrap();
360        assert_eq!(s, r#""x""#);
361    }
362
363    #[cfg(feature = "serialize")]
364    #[test]
365    fn box_serialize_estree() {
366        use oxc_estree::{CompactTSSerializer, ESTree};
367
368        let allocator = Allocator::default();
369        let b = Box::new_in("x", &allocator);
370
371        let mut serializer = CompactTSSerializer::default();
372        b.serialize(&mut serializer);
373        let s = serializer.into_string();
374        assert_eq!(s, r#""x""#);
375    }
376
377    #[test]
378    fn lifetime_variance() {
379        fn _assert_box_variant_lifetime<'a: 'b, 'b, T>(program: Box<'a, T>) -> Box<'b, T> {
380            program
381        }
382    }
383}