如何正确初始化固定长度数组?

42

我在初始化一个固定长度的数组时遇到了问题。到目前为止,我的尝试都导致了相同的错误:"使用可能未初始化的变量:foo_array":

#[derive(Debug)]
struct Foo { a: u32, b: u32 }

impl Default for Foo {
    fn default() -> Foo { Foo{a:1, b:2} }
}

pub fn main() {
    let mut foo_array: [Foo; 10];

    // Do something here to in-place initialize foo_array?

    for f in foo_array.iter() {
        println!("{:?}", f);
    }
}
error[E0381]: use of possibly uninitialized variable: `foo_array`
  --> src/main.rs:13:14
   |
13 |     for f in foo_array.iter() {
   |              ^^^^^^^^^ use of possibly uninitialized `foo_array`

我实现了Default特质,但Rust似乎不会默认调用它,类似于C++构造函数。

有什么正确的方法来初始化一个定长数组吗?我想要进行高效的原地初始化而不是某种类型的复制。

相关:为什么默认(由结构体值)数组初始化需要Copy特质?

相关:有没有方法可以不必两次初始化数组?


2
Rust 似乎不会默认调用它 —— 这是正确的。Default trait 在编译器中没有被特殊使用,它只是为程序员提供的一种工具。 - Shepmaster
3个回答

41

安全但效率有些低下的解决方案

#[derive(Copy, Clone, Debug)]
struct Foo {
    a: u32,
    b: u32,
}

fn main() {
    let mut foo_array = [Foo { a: 10, b: 10 }; 10];
}

由于你特别要求不带副本的解决方案:

use std::mem::MaybeUninit;

#[derive(Debug)]
struct Foo {
    a: u32,
    b: u32,
}

// We're just implementing Drop to prove there are no unnecessary copies.
impl Drop for Foo {
    fn drop(&mut self) {
        println!("Destructor running for a Foo");
    }
}

pub fn main() {
    let array = {
        // Create an array of uninitialized values.
        let mut array: [MaybeUninit<Foo>; 10] = unsafe { MaybeUninit::uninit().assume_init() };

        for (i, element) in array.iter_mut().enumerate() {
            let foo = Foo { a: i as u32, b: 0 };
            *element = MaybeUninit::new(foo);
        }

        unsafe { std::mem::transmute::<_, [Foo; 10]>(array) }
    };

    for element in array.iter() {
        println!("{:?}", element);
    }
}

这是 MaybeUninit文档推荐的。

2
@A.B.:为什么第一个解决方案效率低?(天真的问题,我真的不知道...) - Matthieu M.
2
在需要构建元素彼此不同的数组的情况下,例如扑克牌组,使用该方法效率低下。在标准的52张牌组中,您最终会进行51次不必要的复制。 - A.B.
6
如果在调用mem::uninitialized()和数组完全初始化之间出现恐慌的机会,那么这段代码就是有问题的,并且不安全。不过,如果Foo是“POD”类型,则没有问题。请注意,一旦引入泛型(并在初始化循环中调用trait方法),那么很可能无法保证不出现恐慌。 - bluss
1
在安全版本中:由于初始化的值从未被读取,编译器是否可以优化掉该副本?(或者可能吗?) - Christopher Stevenson
1
简单的事情变得复杂 - 这就是 Rust 的方式。说真的,我想知道有多少人在遇到看似容易的情况(在其他语言中很容易)却被卡住并浪费时间后放弃了 Rust。我偶尔还会回到 Rust,但我仍然无法忘记这个事实:我不能用枚举值创建一个编译时查找表,只是因为 Rust 认为在访问时将它们移动是个好主意。 - BitTickler
显示剩余3条评论

11

最简单的方法是对您的类型派生Copy,并用该类型初始化数组,将元素复制N次:

#[derive(Copy)]
struct Foo {
    a: u32,
    b: u32,
}

let mut foo_array = [Foo { a: 1, b: 2 }; 10];

如果您想避免复制,有几个选项。您可以使用 Default 特性:

let mut foo_array: [Foo; 10] = Default::default();

然而,这仅限于最多32个元素的数组。使用const泛型,标准库现在可以为所有数组提供Default。不过,由于一些微妙的原因,这将是一个不兼容的变化,目前正在努力解决中。

目前,您可以利用const值也被允许在数组重复表达式中的事实:

const FOO: Foo = Foo { a: 1, b: 2 };

let mut foo_array = [FOO; 10];

如果您使用的是夜间版,您可以使用array::map函数:
#![feature(array_map)]

let mut foo_array = [(); 10].map(|_| Foo::default())

你能详细说明一下或者提供一个关于Default反向兼容性问题的工单吗?这些问题对我来说并不明显。 - TheCycoONE
1
@TheCycoONE 目前有一个 impl<T> Default for [T; 0]。请注意,它不需要 T: Default,因为它实际上并没有创建一个 T。使用 const 泛型,目前还没有办法专门化该 impl,因此通用的 impl<T: Default, const N: usize> Default for [T; N] 将会被破坏。 - Ibraheem Ahmed
2
array::map 已经稳定了!现在它是这个问题的最佳答案。 - sffc
还有core::array::from_fn,它可以为您提供索引。 - Keavon

8
你可以使用 arrayvec crateCargo.toml
[package]
name = "initialize_array"
version = "0.1.0"
edition = "2018"

[dependencies]
arrayvec = "0.7.2"

src/main.rs

use arrayvec::ArrayVec; 
use std::iter;

#[derive(Clone)]
struct Foo {
    a: u32,
    b: u32,
}

fn main() {
    let foo_array: [Foo; 10] = iter::repeat(Foo { a: 10, b: 10 })
        .take(10)
        .collect::<ArrayVec<_, 10>>()
        .into_inner()
        .unwrap_or_else(|_| unreachable!());
}

1
我认为你的代码在collect之前缺少了.take(10) - Lukas Kalbertodt
@LukasKalbertodt 对于较新版本的arrayvec,确实需要这样做。我已将示例更新为arrayvec 0.7.2。 - heinrich5991

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