feat(allocator): add methods for boxed slices ArenaBox<[T]> (#19968)

Add methods to allocator `Box` to make it easier to work with boxed slices.

* `Box::<[T]>::new_empty_boxed_slice` - create a new `Box<[T]>` with an empty slice (equivalent to `Vec::new_in`).
* `Box::<[T]>::into_bump_slice` + `Box::<[T]>::into_bump_slice_mut` - convert a boxed slice to a `&[T]` / `&mut [T]` with lifetime of the arena (equivalent to same methods on `Vec`).
* Implement `Dummy` and `TakeIn` for boxed slices.
This commit is contained in:
overlookmotel committed 2026-03-03 16:23:30 +00:00
1 parent 7502afec08
commit 8345318d0a
2 files changed
+99 -3

No files matched your search

+89 -3
View File
@@ -7,6 +7,7 @@ use std::{
fmt::{self, Debug, Display, Formatter},
hash::{Hash, Hasher},
marker::PhantomData,
mem,
ops::{Deref, DerefMut},
ptr::{self, NonNull},
};
@@ -149,12 +150,68 @@ impl<T: ?Sized> Box<'_, T> {
}
}
impl<T> Box<'static, [T]> {
/// Create a new empty `Box<[T]>`.
///
/// This method does not allocate. The returned boxed slice is represented by a dangling,
/// correctly-aligned pointer with length 0, similar to how `Vec::new_in` produces an empty vector.
#[inline]
pub fn new_empty_boxed_slice() -> Self {
const { Self::ASSERT_T_IS_NOT_DROP };
// `NonNull::<T>::dangling()` yields a non-null, properly aligned pointer.
// We pair it with length 0 to construct a `NonNull<[T]>` representing an empty slice.
// Correct alignment is the only requirement for it to be sound to dereference this pointer
// to a slice, because the slice is empty.
// See: https://doc.rust-lang.org/std/slice/fn.from_raw_parts.html
let ptr = NonNull::dangling();
let slice_ptr = NonNull::slice_from_raw_parts(ptr, 0);
Self(slice_ptr, PhantomData)
}
}
impl<'a, T> Box<'a, [T]> {
/// Convert a boxed slice [`Box<[T]>`] into slice [`&'a [T]`].
///
/// The returned slice has the same lifetime as the allocator.
//
// `#[inline(always)]` because this is a no-op. `Box<[T]>` and `&[T]` have the same layout.
#[expect(clippy::inline_always)]
#[inline(always)]
pub fn into_bump_slice(self) -> &'a [T] {
let r = self.as_ref();
// Extend lifetime of reference to lifetime of the allocator.
// SAFETY: `self` is consumed by this method, so there cannot be any mutable references to it.
// The reference lives until the allocator is dropped or reset (`'a` lifetime).
// Don't need `mem::forget(self)` here, because `Box` does not implement `Drop`.
unsafe { mem::transmute::<&[T], &'a [T]>(r) }
}
/// Convert a boxed slice [`Box<[T]>`] into mutable slice [`&'a mut [T]`].
///
/// The returned slice has the same lifetime as the allocator.
//
// `#[inline(always)]` because this is a no-op. `Box<[T]>` and `&mut [T]` have the same layout.
#[expect(clippy::inline_always)]
#[inline(always)]
pub fn into_bump_slice_mut(mut self) -> &'a mut [T] {
let r = self.as_mut();
// Extend lifetime of reference to lifetime of the allocator.
// SAFETY: `self` is consumed by this method, so there cannot be any other references to it.
// The reference lives until the allocator is dropped or reset (`'a` lifetime).
// Don't need `mem::forget(self)` here, because `Box` does not implement `Drop`.
unsafe { mem::transmute::<&mut [T], &'a mut [T]>(r) }
}
}
impl<T: ?Sized> Deref for Box<'_, T> {
type Target = T;
#[inline]
fn deref(&self) -> &T {
// SAFETY: self.0 is always a unique reference allocated from a Bump in Box::new_in
// SAFETY: `self.0` is always a unique reference allocated from a `Bump` in `Box::new_in`,
// or an empty slice allocated from `Box::new_empty_boxed_slice`
unsafe { self.0.as_ref() }
}
}
@@ -162,7 +219,8 @@ impl<T: ?Sized> Deref for Box<'_, T> {
impl<T: ?Sized> DerefMut for Box<'_, T> {
#[inline]
fn deref_mut(&mut self) -> &mut T {
// SAFETY: self.0 is always a unique reference allocated from a Bump in Box::new_in
// SAFETY: `self.0` is always a unique reference allocated from a `Bump` in `Box::new_in`,
// or an empty slice allocated from `Box::new_empty_boxed_slice`
unsafe { self.0.as_mut() }
}
}
@@ -230,8 +288,9 @@ impl<T: Hash> Hash for Box<'_, T> {
mod test {
use std::hash::{DefaultHasher, Hash, Hasher};
use crate::{Allocator, Vec};
use super::Box;
use crate::Allocator;
#[test]
fn box_deref_mut() {
@@ -242,6 +301,33 @@ mod test {
assert_eq!(*b, "v");
}
#[test]
fn new_empty_boxed_slice() {
let b = Box::<[u32]>::new_empty_boxed_slice();
assert!(b.is_empty());
assert_eq!(b.len(), 0);
assert_eq!(&*b, &[] as &[u32]);
}
#[test]
fn boxed_slice_into_bump_slice() {
let allocator = Allocator::default();
let v = Vec::from_iter_in([1, 2, 3], &allocator);
let b = v.into_boxed_slice();
let slice = b.into_bump_slice();
assert_eq!(slice, &[1, 2, 3]);
}
#[test]
fn boxed_slice_into_bump_slice_mut() {
let allocator = Allocator::default();
let v = Vec::from_iter_in([10, 20, 30], &allocator);
let b = v.into_boxed_slice();
let slice = b.into_bump_slice_mut();
slice[1] = 99;
assert_eq!(slice, &[10, 99, 30]);
}
#[test]
fn box_debug() {
let allocator = Allocator::default();
+10
View File
@@ -23,6 +23,8 @@ pub trait TakeIn<'a>: Dummy<'a> {
impl<'a, T> TakeIn<'a> for Vec<'a, T> {}
impl<'a, T> TakeIn<'a> for Box<'a, [T]> {}
/// A trait to create a dummy AST node.
pub trait Dummy<'a>: Sized {
/// Create a dummy node.
@@ -46,6 +48,14 @@ impl<'a, T: Dummy<'a>> Dummy<'a> for Box<'a, T> {
}
}
impl<'a, T> Dummy<'a> for Box<'a, [T]> {
/// Create a dummy empty [`Box<[T]>`] boxed slice.
#[inline]
fn dummy(_allocator: &'a Allocator) -> Self {
Box::new_empty_boxed_slice()
}
}
impl<'a, T> Dummy<'a> for Vec<'a, T> {
/// Create a dummy [`Vec`].
#[inline]