10

Just started with rust and I'm trying some stuff with arrays and running into some difficulties. The first issue is initialization. I can create an "empty" array and iterate over elements to populate it, but how can I initialize from a range?:

let mut arr: [i32; 5] = [0; 5]; // this works
// let mut arr: [i32; 5] = 1..5 //this doesn't

The second problem is trying to initialize an array from a simple map over an array on i32. This doesn't work:

let mut arr2: [i32; 5] =  arr.iter().map(|&x| x + 1).collect();

Indeed even printing (without assigning) doesn't work:

println!("{:?}", arr.iter().map(|&x| x + 1).collect());
// the above fails with: "type annotations needed: cannot infer type for type arameter `B` declared on the associated function `collect`"

Any words of wisdom to offer?

boogie
  • 349
  • 1
  • 3
  • 11

2 Answers2

17

As of Rust 1.63

Use from_fn:

let array: [usize; 5] = core::array::from_fn(|i| i + 1);
assert_eq!(array, [1, 2, 3, 4, 5]);
aedm
  • 5,596
  • 2
  • 32
  • 36
13

The general problem here is that an array has a fixed size, known at compile-time, but the number of items in an iterator is not known at compile-time — even if it is an ExactSizeIterator, there's no const parameter for the size. Therefore, there are no infallible conversions from an iterator to an array.

However, there are fallible conversions available. The most straightforward commonly-useful one is impl<T, A, const N: usize> TryFrom<Vec<T, A>> for [T; N], which allows you to convert a Vec<T> to [T; N], failing if the vector is of the wrong length.

use std::convert::TryInto;

let arr: [i32; 5] = (1..=5).collect::<Vec<_>>()
    .try_into().expect("wrong size iterator");

This has the disadvantage of making a temporary heap allocation for the Vec and discarding it. If you do want the array to live on the heap, then you might want to use TryFrom<Box<[T], Global>> for Box<[T; N], Global> instead, which allows you to get a boxed array from a boxed slice.

let arr: Box<[i32; 5]> = (1..=5)
    .collect::<Box<[i32]>>()
    .try_into().expect("wrong size iterator");

If you want the array to be stack allocated and to avoid temporary allocations, then you will have to work with std::iter::from_fn(), slightly awkwardly:

let mut iter = 1..=5;
let arr: [i32; 5] = std::array::from_fn(|_| iter.next().expect("too short"));
assert!(iter.next().is_none(), "too long");

(If you're confident in the length then there's no need to assert afterward, but an expect/unwrap inside is unavoidable.)


The second problem is trying to initialize an array from a simple map over an array on i32. This doesn't work:

let mut arr2: [i32; 5] =  arr.iter().map(|&x| x + 1).collect();

This doesn't work for the same reason as the first: you can't currently collect directly into an array. Any of the techniques I mentioned above will also work for this case.

Since this question and answer were originally written, arrays have now gained a method map which you can use to do this without going through an iterator:

let mut arr2: [i32; 5] = arr.map(|x| x + 1);

Indeed even printing (without assigning) doesn't work:

This is actually a different problem than the others. println!("{:?}", some_iterator.collect()) will always fail, because you haven't specified what type to collect into. collect() never assumes a collection type; it always has to be specified or inferred from context.

If you specify a type, then you can print the results of the iterator; whether the original input was an array is irrelevant.

println!("{:?}", arr.iter().map(|&x| x + 1).collect::<Vec<_>>());
Kevin Reid
  • 37,492
  • 13
  • 80
  • 108