Je souhaite appeler .map()
sur un tableau d'enum:
enum Foo {
Value(i32),
Nothing,
}
fn main() {
let bar = [1, 2, 3];
let foos = bar.iter().map(|x| Foo::Value(*x)).collect::<[Foo; 3]>();
}
mais le compilateur se plaint:
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]`
Comment puis-je faire cela?
Le problème est en réalité dans collect
, pas dans map
.
Pour pouvoir collecter les résultats d'une itération dans un conteneur, ce dernier doit implémenter FromIterator
.
[T; n]
n'implémente pas FromIterator
car il ne peut généralement pas le faire: pour produire un [T; n]
, vous devez fournir exactement les éléments n
. Toutefois, lorsque vous utilisez FromIterator
, vous ne donnez aucune garantie quant au nombre d'éléments qui seront introduits dans votre type.
Il y a aussi la difficulté que vous ne sauriez pas, sans données supplémentaires, quel index du tableau vous devriez nourrir maintenant (et s'il est vide ou plein), etc ... ceci pourrait être résolu en utilisant enumerate
après map
(essentiellement l’index), mais vous auriez toujours le problème de décider quoi faire si pas assez ou trop d’éléments sont fournis.
Par conséquent, non seulement pour le moment on ne peut pas implémenter FromIterator
sur un tableau de taille fixe; mais même dans le futur, cela semble être un long plan.
Alors maintenant, que faire? Il y a plusieurs possibilités:
[Value(1), Value(2), Value(3)]
, éventuellement à l'aide d'une macroVec<Foo>
Dans ce cas, vous pouvez utiliser Vec<Foo>
:
#[derive(Debug)]
enum Foo {
Value(i32),
Nothing,
}
fn main() {
let bar = [1, 2, 3];
let foos = bar.iter().map(|&x| Foo::Value(x)).collect::<Vec<Foo>>();
println!("{:?}", foos);
}
Cela n'est pas possible car les tableaux n'implémentent aucun trait. Vous ne pouvez collecter que dans des types qui implémentent le trait FromIterator
(voir la liste au bas de ses docs ).
C'est une limitation de la langue, car il est actuellement impossible d'être générique sur la longueur d'un tableau et que la longueur fait partie de son type. Mais, même si étaient possibles, il est très peu probable que FromIterator
soit implémenté sur des tableaux car il devrait paniquer si le nombre d'éléments générés n'est pas exactement la longueur du tableau.
Bien que vous ne puissiez pas collecter directement dans un tableau pour les raisons indiquées par les autres réponses, cela ne signifie pas que vous ne pouvez pas collecter dans une structure de données backed par un tableau, comme un ArrayVec
:
extern crate arrayvec;
use arrayvec::ArrayVec;
enum Foo {
Value(i32),
Nothing,
}
fn main() {
let bar = [1, 2, 3];
let foos: ArrayVec<[_; 3]> = bar.iter().map(|x| Foo::Value(*x)).collect();
let the_array = foos.into_inner()
.unwrap_or_else(|_| panic!("Array was not completely filled"));
}
En tirant le tableau de la ArrayVec
, on retourne une Result
pour traiter le cas où il n’y avait pas assez d’éléments pour le remplir; le cas qui a été discuté dans les autres réponses.
into_inner
a une mise en garde:
Remarque: Cette fonction peut entraîner une surcharge excessive pour déplacer la matrice vers l'extérieur. Ses performances ne sont pas optimales.
Pour cette raison, vous souhaitez simplement laisser les données là où elles se trouvent. Vous auriez toujours évité l'allocation de tas.
.collect()
construit des structures de données pouvant avoir une longueur arbitraire, car le numéro d'article de l'itérateur n'est pas limité en général. (La réponse de Shepmaster fournit déjà beaucoup de détails ici).
Une possibilité pour obtenir des données dans un tableau à partir d'une chaîne mappée sans allouer une Vec
ou similaire est d'apporter des références mutables au tableau dans la chaîne. Dans votre exemple, cela ressemblerait à ceci:
#[derive(Debug, Clone, Copy)]
enum Foo {
Value(i32),
Nothing,
}
fn main() {
let bar = [1, 2, 3];
let mut foos = [Foo::Nothing; 3];
bar.iter().map(|x| Foo::Value(*x))
.Zip(foos.iter_mut()).for_each(|(b, df)| *df = b);
}
La .Zip()
exécute l'itération sur bar
et foos
de manière uniforme: si foos
était sous-alloué, les bar
s les plus élevées ne seraient pas mappées, et si elles étaient trop allouées, ses valeurs d'initialisation d'origine seraient conservées. (Ainsi, les fonctions Cloner et Copier sont également nécessaires pour l’initialisation [Nothing; 3]
).
J'ai rencontré ce problème moi-même - voici une solution de contournement.
Vous ne pouvez pas utiliser FromIterator
, mais vous pouvez effectuer une itération sur le contenu d'un objet de taille fixe ou, si les choses sont plus compliquées, des index découpant tout ce qui peut être consulté. De toute façon, la mutation est viable.
Par exemple, le problème que j'ai eu était avec un tableau de type [[usize; 2]; 4]
:
fn main() {
// Some input that could come from another function and thus not be mutable
let pairs: [[usize; 2]; 4] = [[0, 0], [0, 1], [1, 1], [1, 0]];
// Copy mutable
let mut foo_pairs = pairs.clone();
for pair in foo_pairs.iter_mut() {
// Do some operation or other on the fixed-size contents of each
pair[0] += 1;
pair[1] -= 1;
}
// Go forth and foo the foo_pairs
}
Si cela se passe dans une petite fonction, ça va dans mon livre. Dans les deux cas, vous alliez vous retrouver avec une valeur transformée de type identique à celle du même. Copier tout d’abord puis muter revient à peu près au même effort d’effort que de référencer une valeur dans une fermeture et d’en retourner une fonction. .
Notez que cela ne fonctionne que si vous prévoyez de calculer quelque chose du même type, jusqu'à la taille/longueur comprise. Mais cela sous-entend par votre utilisation des tableaux de Rust. (Plus précisément, vous pourriez Value()
votre Foo
s ou Nothing
comme vous le souhaitez, tout en restant dans les paramètres de type de votre tableau.)