40

I'm having trouble initializing a fixed length array. My attempts so far all result in the same "use of possibly uninitialized variable: foo_array" error:

#[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`

I implemented the Default trait, but Rust does not seem to call this by default akin to a C++ constructor.

What is the proper way to initialize a fixed length array? I'd like to do an efficient in-place initialization rather than some sort of copy.

Related: Why is the Copy trait needed for default (struct valued) array initialization?

Related: Is there a way to not have to initialize arrays twice?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
srking
  • 4,512
  • 1
  • 30
  • 46
  • 2
    *Rust does not seem to call this by default* — that is correct. The `Default` trait is not used by the compiler in any special way. It's only use is for the programmer. – Shepmaster Jul 12 '15 at 14:47

3 Answers3

41

The safe but somewhat inefficient solution:

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

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

Since you're specifically asking for a solution without copies:

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);
    }
}

This is recommended by the documentation of MaybeUninit.

mcarton
  • 27,633
  • 5
  • 85
  • 95
A.B.
  • 15,364
  • 3
  • 61
  • 64
  • 2
    @A.B.: Why is the first solution inefficient? (naive question, I really have no idea...) – Matthieu M. Jul 13 '15 at 12:42
  • 2
    It's inefficient in cases where you need to construct an array in which the elements differ from each other, say set of poker cards. In a standard 52 card set you'd end up doing 51 unnecessary copies. – A.B. Jul 13 '15 at 12:52
  • 6
    If you have any chance of panic between the call to `mem::uninitialized()` and the point where the array is fully initialized, then this code is broken and not panic safe. If Foo is a "POD" type then it's fine, though. Note that as soon as you introduce generics (and call to trait methods in the initialization loop), then you probably have no way of guaranteeing lack of panic anymore. – bluss Jul 13 '15 at 14:04
  • 1
    In the safe version: since the initialized value is never read from, is the copy be optimized away by the compiler? (Or could it?) – Christopher Stevenson Aug 02 '15 at 06:41
  • Version 1 won't work if `Foo` has a `String`, and it's impractical if `Foo` has 1000 members. Version 2 might as well be written in C. There has to be a better way, right? – James M. Lay Mar 11 '18 at 06:30
  • Is "it might as well be C" so bad? The rest of the program is "still Rust". Of course safe implementations are great when we have them. – bluss Mar 16 '18 at 20:14
  • Simple things done complicated - that is the Rust way. Seriously, I wonder how many gave up on Rust after stumbling into seemingly easy (easy in other languages) situations, where they get stuck and waste their time. I still return to Rust once in a while, but I am still not over the fact, that I could not make a compile time lookup table with enum values in it, just because Rust thought it is a good idea to move them on access. – BitTickler Dec 27 '19 at 13:33
  • i did a small benchmark and found that `MaybeUninit::uninit().assume_init()` is ~100x faster than initializing a an array with the same values like `[0.0; 5]` – yes Nov 29 '22 at 09:38
10

The easiest way is to derive Copy on your type and initialize the array with that, copying the element N times:

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

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

If you want to avoid copying, there are a couple options. You can use the Default trait:

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

However, this is limited to arrays up to 32 elements. With const generics, it is now possible for the standard library to provide Default for all arrays. However, this would be a backward incompatible change for subtle reasons that are being worked on.

For now, you can take advantage of the fact that const values are also allowed in array repetition expressions:

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

let mut foo_array = [FOO; 10];

If you're on nightly, you can use array::map:

#![feature(array_map)]

let mut foo_array = [(); 10].map(|_| Foo::default())
Ibraheem Ahmed
  • 11,652
  • 2
  • 48
  • 54
  • Can you elaborate on or point to a ticket for the backwards compatibility concerns with Default. They aren't obvious to me. – TheCycoONE Dec 07 '21 at 03:27
  • 1
    @TheCycoONE Right now there is an `impl Default for [T; 0]`. Note that it doesn't require `T: Default`, because it doesn't actually create a `T`. WIth const generics, there is currently no way to specialize that impl, so the blanket `impl Default for [T; N]` would be breaking. – Ibraheem Ahmed Dec 14 '21 at 23:38
  • 1
    `array::map` landed on stable! This is now the best answer to the question. – sffc Jan 04 '22 at 05:44
  • There is also [core::array::from_fn](https://doc.rust-lang.org/core/array/fn.from_fn.html) which provides you an index. – Keavon Apr 13 '23 at 22:38
8

You can use the arrayvec crate:

Cargo.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!());
}
heinrich5991
  • 2,694
  • 1
  • 19
  • 26