web-dev-qa-db-fra.com

Comment puis-je rassembler dans un tableau?

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?

13
rausch

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:

  • inline la transformation sur le site d'appel: [Value(1), Value(2), Value(3)], éventuellement à l'aide d'une macro
  • collecter dans un autre conteneur (susceptible d'être développé), tel que Vec<Foo>
  • ...
21
Matthieu M.

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);
}
6
robitex

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.

4
Corey Richardson

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.

2
Shepmaster

.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 bars 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]).

2
chrysn

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 Foos ou Nothing comme vous le souhaitez, tout en restant dans les paramètres de type de votre tableau.)

0
bright-star