在Rust中在堆上创建一个固定大小的数组

9

我尝试使用以下代码:

fn main() {
    let array = box [1, 2, 3];
}

在我的程序中,出现了编译错误:error: obsolete syntax: ~[T] is no longer a type。据我所知,在Rust中没有动态大小的数组(大小必须在编译时已知)。然而,在我的代码片段中,数组确实具有静态大小,并且应该是~[T, ..3]类型(大小为3的拥有静态数组),但编译器却说它的类型是~[T]。是否存在深层次原因,导致无法在堆上分配静态大小的数组?附注:是的,我听说过Vec

5
这可能只是从 ~ 到 box 的转换的副作用。最新的夜间版可以满足你的需求。 - Arjan
@arjan 很酷,谢谢,我会试试夜间版! - karlicoss
@Arjan 是的,它确实有效。谢谢! - karlicoss
那个版本的解决方法是将 box () [1, 2, 3] 重写为可能需要在 [1, 2, 3] 周围加上额外括号的形式。但现在你不需要这样做。 - Chris Morgan
3个回答

16
自从我来到这里,其他人也可能会来到这里。Rust已经发展了起来,在本答案发布时,Rust稳定版本为1.53,夜间版本为1.55。
使用Box::new([1, 2, 3])是推荐的方式,它能够完成任务,但有一个注意点:数组首先在栈上被创建,然后再复制到堆上。这是Box文档中所记录的行为: 通过创建Box将一个值从栈移动到堆上: 这意味着它包含了一个隐藏的 memcopy,当处理大型数组时,堆分配甚至会因堆栈溢出而失败。
const X: usize = 10_000_000;
let failing_huge_heap_array = [1; X];

thread 'main' has overflowed its stack
fatal runtime error: stack overflow

目前(Rust 1.53),有几种解决方法,最简单的方法是创建一个向量并将该向量转换为一个盒式切片:

const X: usize = 10_000_000;
let huge_heap_array = vec![1; X].into_boxed_slice();

这个方法是可行的,但有两个小问题:它丢失了类型信息,本应是Box<[i32; 10000000]>现在变成了Box<[usize]>,此外在栈上占用了16个字节,而数组只占用8个字节。

...
println!("{}", mem::size_of_val(&huge_heap_array);

16

可能不是什么大问题,但这违背了我的个人修行。

进一步研究后,我排除了那些需要像OPbox [1, 2, 3]那样使用#![feature(box_syntax)]功能和arr crate(也需要夜间版本)的选项,我发现在堆上分配数组且没有隐藏的memcopy的最佳解决方案是由Simias提出的建议。

/// A macro similar to `vec![$elem; $size]` which returns a boxed array.
///
/// ```rustc
///     let _: Box<[u8; 1024]> = box_array![0; 1024];
/// ```
macro_rules! box_array {
    ($val:expr ; $len:expr) => {{
        // Use a generic function so that the pointer cast remains type-safe
        fn vec_to_boxed_array<T>(vec: Vec<T>) -> Box<[T; $len]> {
            let boxed_slice = vec.into_boxed_slice();

            let ptr = ::std::boxed::Box::into_raw(boxed_slice) as *mut [T; $len];

            unsafe { Box::from_raw(ptr) }
        }

        vec_to_boxed_array(vec![$val; $len])
    }};
}

const X: usize = 10_000_000;
let huge_heap_array = box_array![1; X];

它不会溢出堆栈,只占用8个字节,同时保留类型。

它使用了不安全的操作,但将其限制在单行代码中。在 box [1;X] 语法到来之前,我认为这是一种干净的选择。


基本上与 unsafe { Box::<[T; $len]>::try_from(vec![$val; $len].into_boxed_slice()).unwrap_unchecked() } 相同,不是吗? - Des Nerger

5
据我所知,box表达式仍处于实验阶段。您可以使用以下代码的形式Box::new()来消除警告信息。
fn main() {
    let array1 = Box::new([1, 2, 3]);
    // or even
    let array2: Box<[i32]> = Box::new([1, 2, 3]);
}

请查看下面Shepmaster的评论,因为它们是不同类型。

4
需要注意的是,这些是稍微不同类型。array1 是一个 Box<[_; 3]> - 一个精确包含3个元素的堆分配数组;其中每个项目都是未确定的整数类型。array2 是一个 Box<[i32]>,一个堆分配的32位有符号整数切片。 - Shepmaster
@Shepmaster 感谢您的留言。非常好的发现! - Daniel Robertson

-5
只需像这样编写代码:let mut buffer= vec![0; k]; 它将创建一个长度为k的u8数组。

5
这不是创建一个数组,而是创建一个向量。在Rust中,数组向量是不同的类型。具体来说,在数组中,容量项目数 都是固定的,而向量没有固定的值。 - Shepmaster

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接