I want to call .map()
on an array of enums:
enum Foo {
Value(i32),
Nothing,
}
fn main() {
let bar = [1, 2, 3];
let foos = bar.iter().map(|x| Foo::Value(*x)).collect::<[Foo; 3]>();
}
but the compiler complains:
error[E0277]: the trait bound `[Foo; 3]: std::iter::FromIterator<Foo>` is not satisfied
--> src/main.rs:8:51
|
8 | let foos = bar.iter().map(|x| Foo::Value(*x)).collect::<[Foo; 3]>();
| ^^^^^^^ a collection of type `[Foo; 3]` cannot be built from an iterator over elements of type `Foo`
|
= help: the trait `std::iter::FromIterator<Foo>` is not implemented for `[Foo; 3]`
How do I do this?
This isn't possible because arrays do not implement any traits. You can only collect into types which implement the
FromIterator
trait (see the list at the bottom of its docs).This is a language limitation, since it's currently impossible to be generic over the length of an array and the length is part of its type. But, even if it were possible, it's very unlikely that
FromIterator
would be implemented on arrays because it'd have to panic if the number of items yielded wasn't exactly the length of the array.I ran into this problem myself — here's a workaround.
You can't use
FromIterator
, but you can iterate over the contents of a fixed-size object, or, if things are more complicated, indices that slice anything that can be accessed. Either way, mutation is viable.For example, the problem I had was with an array of type
[[usize; 2]; 4]
:If this is happening inside a small function, it's okay in my book. Either way, you were going to end up with a transformed value of identical type as the same one, so copying the whole thing first and then mutating is about the same amount of effort as referencing a value in a closure and returning some function of it.
Note that this only works if you plan to compute something that is going to be the same type, up to and including size/length. But that's implied by your use of Rust arrays. (Specifically, you could
Value()
yourFoo
s orNothing
them as you like, and still be within type parameters for your array.)While you cannot directly collect into an array for the reasons stated by the other answers, that doesn't mean that you can't collect into a data structure backed by an array, like an
ArrayVec
:Pulling the array out of the
ArrayVec
returns aResult
to deal with the case where there weren't enough items to fill it; the case that was discussed in the other answers.into_inner
does have a caveat:Because of this, you way wish to just leave the data where it is. You'd still have avoided heap allocation.
The issue is actually in
collect
, not inmap
.In order to be able to collect the results of an iteration into a container, this container should implement
FromIterator
.[T; n]
does not implementFromIterator
because it cannot do so generally: to produce a[T; n]
you need to providen
elements exactly, however when usingFromIterator
you make no guarantee about the number of elements that will be fed into your type.There is also the difficulty that you would not know, without supplementary data, which index of the array you should be feeding now (and whether it's empty or full), etc... this could be addressed by using
enumerate
aftermap
(essentially feeding the index), but then you would still have the issue of deciding what to do if not enough or too many elements are supplied.Therefore, not only at the moment one cannot implement
FromIterator
on a fixed-size array; but even in the future it seems like a long shot.So, now what to do? There are several possibilities:
[Value(1), Value(2), Value(3)]
, possibly with the help of a macroVec<Foo>
.collect()
builds data structures that can have arbitrary length, because the iterator's item number is not limited in general. (Shepmaster's answer already provides plenty details there).One possibility to get data into an array from a mapped chain without allocating a
Vec
or similar is to bring mutable references to the array into the chain. In your example, that'd look like this:The
.zip()
makes the iteration run over bothbar
andfoos
in lockstep -- iffoos
were under-allocated, the higherbar
s would not be mapped at all, and if it were over-allocated, it'd keep its original initialization values. (Thus also the Clone and Copy, they are needed for the[Nothing; 3]
initialization).In this case you can use
Vec<Foo>
: