Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

rust array initialization from range or collect

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?

like image 839
boogie Avatar asked Dec 05 '25 08:12

boogie


2 Answers

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]);
like image 122
aedm Avatar answered Dec 08 '25 23:12

aedm


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<_>>());
like image 31
Kevin Reid Avatar answered Dec 08 '25 21:12

Kevin Reid



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!