How to filter a vector of custom structs in Rust?

Zelphir Kaltstahl picture Zelphir Kaltstahl · Jun 20, 2017 · Viewed 17.9k times · Source

I am trying to filter a Vec<Vocabulary> where Vocabulary is a custom struct, which itself contains a struct VocabularyMetadata and a Vec<Word>:

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

This is for handling a route in a web application, where the route looks like this: /word/<vocabulary_id>/<word_id>.

Here is my current code trying to filter the Vec<Vocabulary>:

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

This does not work. The error I get is:

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

I don't know how to implement any FromIterator, nor why that would be necessary. In another route in the same web app, same file I do the following, which works:

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

So it seems that String implements FromIterator.

However, I don't get, why I cannot simple get back the Elements of the Vec from the filter or collect method.

How can I filter my Vec and simply get the elements of the Vec<Vocabulary>, for which the condition is true?

Answer

Shepmaster picture Shepmaster · Jun 20, 2017

It's very important programming skill to learn how to create a minimal, reproducible example. Your problem can be reduced to this:

struct Vocabulary;

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

Let's look at the error message for your case:

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>`

This says that a Vec<Vocabulary> cannot be built from an iterator of &Vocabulary. Do you see the difference? You have an iterator of references (&), not an iterator of values. How would Vec know how to convert your references into values?


How do you fix it? I don't know what works best in your situation:

  1. Don't iterate over references, iterate over the values themselves. The default choice requires that you have ownership of the vector. Use into_iter instead of iter:

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

    You could also drain the iterator if you have a mutable reference:

    let the_vocabulary: Vec<Vocabulary> = vocabulary_context
        .vocabularies
        .drain(..)
        .filter(|voc| voc.metadata.identifier == vocabulary_id)
        .collect();
    
  2. Duplicate the objects by cloning them. This requires that the type you are iterating on implements Clone. If you pair this with filtering, you should call cloned() after filtering and before calling collect() to avoid cloning something you discard.

    let the_vocabulary: Vec<Vocabulary> = vocabulary_context
        .vocabularies
        .iter()
        .filter(|voc| voc.metadata.identifier == vocabulary_id)
        .cloned()
        .collect();
    
  3. Don't collect values, collect a Vec of references. This requires that however you use the items afterwards can take an item by reference instead of by value:

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

Note that I removed the redundant type specifiers (the turbofish ::<> on collect). You only need to specify the type of the variable or on collect, not both. In fact, all three examples could start with let the_vocabulary: Vec<_> to let the compiler infer the type inside the collection based on the iterator. This is the idiomatic style but I've kept the explicit types for demonstration purposes.


See also: