在 `[T; 时在 Rust 中创建一个 `Pin<Box<[T; N]>>` N]` 太大,无法在堆栈上创建

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

笼统的问题

如何在稳定的 Rust 中实现通用函数 pinned_array_of_default,其中 [T; N] 太大而无法放入堆栈?

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

或者,T 可以实施 Copy 如果这会使过程更容易。

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

将解决方案保存在安全的 Rust 中会更好,但似乎不太可能。

方法

最初我希望通过实现 Default 我可以让 Default 处理初始分配,但是它仍然在堆栈上创建它所以这不适用于大值N.

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

我怀疑我需要使用 MaybeUninit 来实现这个并且有一个 Box::new_uninit() 功能,但它目前不稳定,我希望将它保持在稳定的 Rust 中。我也有点不确定将 Pin<Box<MaybeUninit<B>>> 转换为 Pin<Box<B>> 是否会对 Pin.

产生某种负面影响

背景

使用 Pin<Box<[T; N]>> 的目的是保存一个指针块,其中 N 是页面大小的某个常量 factor/multiple。

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

每个指针在给定时间点可能正在使用也可能未使用。使用中的 Foo 指向 R,而 unused/empty Foo 有指向块中下一个空 FooNone 的指针].指向块中第一个未使用的 Foo 的指针被单独存储。当一个块已满时,将创建一个新块,然后未使用位置的指针链继续通过下一个块。

盒子需要固定,因为它将包含自引用指针以及将指针保存到每个块中指定位置的外部结构。

我知道 Foo 按照 Rust 标准非常不安全,但是创建 Pin<Box<[T; N]>> 的一般问题仍然存在

一种在堆上构造大型数组并避免在堆栈上创建它的方法是通过 Vec 进行代理。您可以构建元素并使用 .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() 将其转换为 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()
}

如果你添加 T: Clone,你可以选择使它看起来更 straight-forward 这样你就可以 vec![T::default(); N] and/or 添加 T: Debug 这样你就可以使用.unwrap().expect().

另请参阅:

  • Creating a fixed-size array on heap in Rust