Locke
Locke

Reputation: 8972

Creating a `Pin<Box<[T; N]>>` in Rust when `[T; N]` is too large to be created on the stack

Generalized Question

How can I implement a general function pinned_array_of_default in stable Rust where [T; N] is too large to fit on the stack?

fn pinned_array_of_default<T: Default, const N: usize>() -> Pin<Box<[T; N]>> {
    unimplemented!()
}

Alternatively, T can implement Copy if that makes the process easier.

fn pinned_array_of_element<T: Copy, const N: usize>(x: T) -> Pin<Box<[T; N]>> {
    unimplemented!()
}

Keeping the solution in safe Rust would have been preferable, but it seems unlikely that it is possible.

Approaches

Initially I was hopping that by implementing Default I might be able to get Default to handle the initial allocation, however it still creates it on the stack so this will not work for large values of N.

let boxed: Box<[T; N]> = Box::default();
let foo = Pin::new(boxed);

I suspect I need to use MaybeUninit to achieve this and there is a Box::new_uninit() function, but it is currently unstable and I would ideally like to keep this within stable Rust. I also somewhat unsure if transmuting Pin<Box<MaybeUninit<B>>> to Pin<Box<B>> could somehow have negative effects on the Pin.

Background

The purpose behind using a Pin<Box<[T; N]>> is to hold a block of pointers where N is some constant factor/multiple of the page size.

#[repr(C)]
#[derive(Copy, Clone)]
pub union Foo<R: ?Sized> {
    assigned: NonNull<R>,
    next_unused: Option<NonNull<Self>>,
}

Each pointer may or may not be in use at a given point in time. An in-use Foo points to R, and an unused/empty Foo has a pointer to either the next empty Foo in the block or None. A pointer to the first unused Foo in the block is stored separately. When a block is full, a new block is created and then pointer chain of unused positions continues through the next block.

The box needs to be pinned since it will contain self referential pointers as well as outside structs holding pointers into assigned positions in each block.

I know that Foo is wildly unsafe by Rust standards, but the general question of creating a Pin<Box<[T; N]>> still stands

Upvotes: 1

Views: 523

Answers (1)

kmdreko
kmdreko

Reputation: 60482

A way to construct a large array on the heap and avoid creating it on the stack is to proxy through a Vec. You can construct the elements and use .into_boxed_slice() to get a Box<[T]>. You can then use .try_into() to convert it to a Box<[T; N]>. And then use .into() to convert it to a Pin<Box<[T; N]>>:

fn pinned_array_of_default<T: Default, const N: usize>() -> Pin<Box<[T; N]>> {
    let mut vec = vec![];
    vec.resize_with(N, T::default);
    let boxed: Box<[T; N]> = match vec.into_boxed_slice().try_into() {
        Ok(boxed) => boxed,
        Err(_) => unreachable!(),
    };
    
    boxed.into()
}

You can optionally make this look more straight-forward if you add T: Clone so that you can do vec![T::default(); N] and/or add T: Debug so you can use .unwrap() or .expect().

See also:

Upvotes: 7

Related Questions