68

I’m trying to initialize a fixed-size array of some nullable, non-copyable type, like an Option<Box<Thing>> for some kind of Thing. I’d like to pack two of them into a struct without any extra indirection. I’d like to write something like this:

let array: [Option<Box<Thing>>; SIZE] = [None; SIZE];

But it doesn’t work because the [e; n] syntax requires that e implements Copy. Of course, I could expand it into SIZE Nones, but that can be unwieldy when SIZE is large. I don’t believe this can be done with a macro without an unnatural encoding of SIZE. Is there a good way to do it?

Yes, this is easy with unsafe; is there a way to do it without unsafe?

2
  • 2
    specifically for [Option<Box<T>>; N] you can use transmute from a [0; N]: is.gd/CC31YQ Commented Feb 22, 2015 at 12:31
  • I wrote an answer to a similar question stackoverflow.com/questions/36925673/… Commented Apr 29, 2016 at 18:32

9 Answers 9

62

As of Rust 1.79 (released in June 2024), this can be very easily achieved using inline const:

struct Thing;

fn main() {
    const SIZE: usize = 100;
    let _array: [Option<Box<Thing>>; SIZE] = [const { None }; SIZE];
}

Playground

That also works for initializing static variables.

If you need to support older Rust versions, a cleaner alternative to previously posted answers is possible using std::array::from_fn(). That requires Rust 1.63 (released in August 2022) or later and looks like this:

const SIZE: usize = 100;
let array: [Option<Box<Thing>>; SIZE] = std::array::from_fn(|_| None);

Playground

If you need to support Rust versions older than 1.63, or if you need to initialize a static, an alternative approach using an intermediate const initializer works as of Rust 1.38 (released in September 2019):

const SIZE: usize = 100;
const INIT: Option<Box<Thing>> = None; // helper
// also works with static array
let array: [Option<Box<Thing>>; SIZE] = [INIT; SIZE]; 

Playground

The first and the last approach have the limitation that the array item must have a representation that can be evaluated at compile time - a constant, enum variant, empty Vec or String, or a primitive container (enum, tuple) composed of those. None or a tuple of numbers will work, but a non-empty Vec or String won't. The array::from_fn() approach has no such limitation.

All the above examples work with or without the Box; they use Box because that was used in the question. All work for arrays of any size.

Sign up to request clarification or add additional context in comments.

Comments

32

You could use the Default trait to initialize the array with default values:

let array: [Option<Box<Thing>>; SIZE] = Default::default();

See this playground for a working example.

Note that this will only work for arrays with up to 32 elements, because Default::default is only implemented for up to [T; 32]. See https://doc.rust-lang.org/std/default/trait.Default.html#impl-Default-for-%5BT%3B%2032%5D.

9 Comments

Unfortunately this doesn't work if SIZE is a generic parameter since that parameter can exceed 32.
@mallwright Exactly. See the answer stackoverflow.com/a/66776497/3497181 for a more recent solution which also works with generic parameters. See also play.rust-lang.org/…
@rnstlr even the answer you link to fails for me with generic parameters: E0401 > can't use generic parameters from outer function
@AndrewArnott it's difficult to know what your problem is without code. Best to create an example on play.rust-lang.org and link it.
@rnstlr, here you go: play.rust-lang.org/…
|
30

As of Rust 1.55.0 (which introduced <[T; N]>::map()), the following will work:

const SIZE: usize = 100;

#[derive(Debug)]
struct THING { data: i64 }

let array = [(); SIZE].map(|_| Option::<THING>::default());
for x in array {
    println!("x: {:?}", x);
}

Rust Playground

4 Comments

Using the map method comes with some performance considerations, though. The documentation states: "Unfortunately, usages of this method are currently not always optimized as well as they could be. This mainly concerns large arrays, as mapping over small arrays seem to be optimized just fine. Also note that in debug mode (i.e. without any optimizations), this method can use a lot of stack space (a few times the size of the array or more)."
@AlejandroGonzález does it refer to the size of the input or output array? the input array is just ZST.
@SOFe [T]::map() is defined to return an array of the same size as the input, so there is only one size to care about.
Scrap that, it refers to the array length! Honestly, it's a good question I don't know the answer for.
7

I'm copying the answer by chris-morgan and adapting it to match the question better, to follow the recommendation by dbaupp downthread, and to match recent syntax changes:

use std::mem;
use std::ptr;

#[derive(Debug)]
struct Thing {
    number: usize,
}

macro_rules! make_array {
    ($n:expr, $constructor:expr) => {{
        let mut items: [_; $n] = mem::uninitialized();
        for (i, place) in items.iter_mut().enumerate() {
            ptr::write(place, $constructor(i));
        }
        items
    }}
}

const SIZE: usize = 50;

fn main() {
    let items = unsafe { make_array!(SIZE, |i| Box::new(Some(Thing { number: i }))) };
    println!("{:?}", &items[..]);
}

Note the need to use unsafe here: The problem is that if the constructor function panic!s, this would lead to undefined behavior.

1 Comment

Note that this solution doesn't allow initializing static (global) arrays. The question didn't specify if that's actually needed, but it might be relevant to future readers.
3

Go through the heap

If you can create a Vec of your type, you can convert it into an array:

use std::convert::TryInto;

#[derive(Clone)]
struct Thing;
const SIZE: usize = 100;

fn main() {
    let v: Vec<Option<Thing>> = vec![None; SIZE];
    let v: Box<[Option<Thing>; SIZE]> = match v.into_boxed_slice().try_into() {
        Ok(v) => v,
        Err(_) => unreachable!(),
    };
    let v: [Option<Thing>; SIZE] = *v;
}

In many cases, you actually want to leave it as a Vec<T>, Box<[T]>, or Box<[T; N]> as these types all put the data in the heap. Large arrays tend to be... large... and you don't want all that data on the stack.

See also:

Keep it simple

Type out all the values:

struct Thing;
const SIZE: usize = 5;

fn main() {
    let array: [Option<Box<Thing>>; SIZE] = [None, None, None, None, None];
}

You could use a build script to generate this code for you. For an example of this, see:

4 Comments

If n = 256 should I really just copy & paste None 256 times?
@MatejKormuth depends on a lot of factors, but I don't see anything inherently wrong with it. It's annoying, yes, but simple.
One such factor: it does work if SIZE is generic
Was looking for Keep it simple advice, my case array is always 20x20=400 elements. Now I'm looking into how to write a macro which would expand into 400 Nones.
2
let stackoverflow: [Option<&mut ()>;0xDEADBEEF] = std::array::from_fn(|_| None);
dbg!(stackoverflow);

playground

Comments

2

Since 1.79.0, you can use inline const. This is a variant of the answer by @user4815162342, but one that doesn't require you to declare a separate constant and repeat the type:

let array: [Option<Box<Thing>>; SIZE] = [const { None }; SIZE];

Until this is stabilized, you can also use the inline-const crate, but this does require you to repeat the type.

Comments

1

An alternative approach using the arrayvec crate that generalizes easily to situations other than initializing everything with a fixed value:

use arrayvec::ArrayVec;

let array = std::iter::repeat(None)
    .take(SIZE)
    .collect::<ArrayVec<Option<Box<Thing>>, SIZE>>()
    .into_inner()
    .unwrap();

(playground)

Comments

1

As of Rust 1.91.0, the following is possible:

let x= std::array::repeat::<_,10>( Vec::<u8>::new() ) ;

It works with any type supporting Clone instead of the more restrictive Copy. As Rust 1.91.0+ is adopted, it should become the most idiomatic solution.

Additionally, Rust 1.63.0 introduced function std::array::from_fn, which accepts a closure mapping indexes to the new elements. It covers a much broader range of scenarios and should also become idiomatic.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.