web-dev-qa-db-fra.com

Comment filtrer un vecteur de structures personnalisées dans Rust?

J'essaie de filtrer un Vec<Vocabulary>Vocabulary est un struct personnalisé, qui contient lui-même un structVocabularyMetadata et un Vec<Word>:

#[derive(Serialize, Deserialize)]
pub struct Vocabulary {
    pub metadata: VocabularyMetadata,
    pub words: Vec<Word>
}

C'est pour gérer un itinéraire dans une application Web, où l'itinéraire ressemble à ceci: /Word/<vocabulary_id>/<Word_id>.

Voici mon code actuel essayant de filter le Vec<Vocabulary>:

let the_vocabulary: Vec<Vocabulary> = vocabulary_context.vocabularies.iter()
    .filter(|voc| voc.metadata.identifier == vocabulary_id)
    .collect::<Vec<Vocabulary>>();

Cela ne fonctionne pas. L'erreur que j'obtiens est:

 the trait `std::iter::FromIterator<&app_structs::Vocabulary>` is not implemented for `std::vec::Vec<app_structs::Vocabulary>` [E0277]

Je ne sais pas comment implémenter un FromIterator, ni pourquoi cela serait nécessaire. Dans une autre route dans la même application web, même fichier, je fais ce qui suit, qui fonctionne:

let result: Vec<String> = vocabulary_context.vocabularies.iter()
    .filter(|voc| voc.metadata.identifier.as_str().contains(vocabulary_id))
    .map(encode_to_string)
    .collect::<Vec<String>>();
    result.join("\n\n")  // returning

Il semble donc que String implémente FromIterator.

Cependant, je ne comprends pas, pourquoi je ne peux pas simplement récupérer les éléments de Vec à partir de la méthode filter ou collect.

Comment puis-je filter mon Vec et obtenir simplement les éléments du Vec<Vocabulary>, pour lequel la condition est vraie?

13
Zelphir Kaltstahl

C'est une compétence de programmation très importante pour apprendre à créer un exemple minimal et reproductible . Votre problème peut être réduit à ceci:

struct Vocabulary;

fn main() {
    let numbers = vec![Vocabulary];
    let other_numbers: Vec<Vocabulary> = numbers.iter().collect();
}

Regardons le message d'erreur pour votre cas:

error[E0277]: a collection of type `std::vec::Vec<Vocabulary>` cannot be built from an iterator over elements of type `&Vocabulary`
 --> src/main.rs:5:57
  |
5 |     let other_numbers: Vec<Vocabulary> = numbers.iter().collect();
  |                                                         ^^^^^^^ a collection of type `std::vec::Vec<Vocabulary>` cannot be built from `std::iter::Iterator<Item=&Vocabulary>`
  |
  = help: the trait `std::iter::FromIterator<&Vocabulary>` is not implemented for `std::vec::Vec<Vocabulary>`

Cela signifie qu'un Vec<Vocabulary> Ne peut pas être construit à partir d'un itérateur de &Vocabulary. Voyez-vous la différence? Vous avez un itérateur de références (&), pas un itérateur de valeurs. Comment Vec saurait-il convertir vos références en valeurs?


Comment le corrigez-vous? Je ne sais pas ce qui fonctionne le mieux dans votre situation:

  1. Ne parcourez pas les références, parcourez les valeurs elles-mêmes. Le choix par défaut nécessite que vous soyez propriétaire du vecteur. Utilisez into_iter Au lieu de iter:

    let the_vocabulary: Vec<Vocabulary> = vocabulary_context
        .vocabularies
        .into_iter()
        .filter(|voc| voc.metadata.identifier == vocabulary_id)
        .collect();
    

    Vous pouvez également drain l'itérateur si vous avez une référence mutable:

    let the_vocabulary: Vec<Vocabulary> = vocabulary_context
        .vocabularies
        .drain(..)
        .filter(|voc| voc.metadata.identifier == vocabulary_id)
        .collect();
    
  2. Dupliquez les objets en les clonant. Cela nécessite que le type sur lequel vous itérez implémente Clone. Si vous associez cela au filtrage, vous devez appeler cloned() après le filtrage et avant d'appeler collect() pour éviter de cloner quelque chose que vous jetez.

    let the_vocabulary: Vec<Vocabulary> = vocabulary_context
        .vocabularies
        .iter()
        .filter(|voc| voc.metadata.identifier == vocabulary_id)
        .cloned()
        .collect();
    
  3. Ne collectez pas de valeurs, collectez un Vec de références. Cela nécessite toutefois que vous utilisiez les éléments par la suite pour pouvoir prendre un élément par référence plutôt que par valeur:

    let the_vocabulary: Vec<&Vocabulary> = vocabulary_context
        .vocabularies
        .iter()
        .filter(|voc| voc.metadata.identifier == vocabulary_id)
        .collect();
    

Notez que j'ai supprimé les spécificateurs de type redondants (le turbofish ::<> Sur collect). Il vous suffit de spécifier le type de la variable ou sur collect, pas les deux. En fait, les trois exemples peuvent commencer par let the_vocabulary: Vec<_> Pour permettre au compilateur de déduire le type à l'intérieur de la collection en fonction de l'itérateur. C'est le style idiomatique mais j'ai gardé les types explicites à des fins de démonstration.


Voir également:

19
Shepmaster