Daniel
Daniel

Reputation: 221

Rust join filter and map on the same line of code

I want to change the value of a specific attribute of a vector of objects.
my code and logic are as follows:

let mut person_found: Vec<Person> = persons.clone().into_iter().filter(|x| x.id == "3")
                       .map(|x| x.name = "Carlos".to_string()).collect();

println!("original: {:?}", &persons);
println!("modified person: {:?}", &person_found);

But it gives me the following error and I can't understand it well.

error[E0277]: a value of type `Vec<Person>` cannot be built from an iterator over elements of type `()`
  --> src\main.rs:17:45
   |
17 |     .map(|x| x.name = "Carlos".to_string()).collect();
   |                                             ^^^^^^^ value of type `Vec<Person>` cannot be built from `std::iter::Iterator<Item=()>`
   |
   = help: the trait `FromIterator<()>` is not implemented for `Vec<Person>`

Upvotes: 2

Views: 1825

Answers (2)

Netwave
Netwave

Reputation: 42678

You are returning nothing in your map expr line. You can handle with filter_map though:

let mut person_found: Vec<Person> = persons
    .iter()
    .filter_map(|p| 
                (p.id == 3).then_some(Person {
                                     name: String::from("Carlos"),
                                     ..p.clone()}))
    .collect();

Playground

Upvotes: 3

vallentin
vallentin

Reputation: 26157

The result of an assignment is () (the unit value). So if you do y = (x = 123); then x is assigned 123 and y is assigned ().

The Rust Reference has a short sentence stating:

An assignment expression always produces the unit value.

Assignment expressions - Operator expressions - The Rust Reference

You need to change so it explicitly returns x on the next line, for that to work. If you want to mutate x then you also need to change |x| to |mut x|.

let person_found: Vec<Person> = persons
    .clone()
    .into_iter()
    .filter(|x| x.id == "3")
    .map(|mut x| {
        x.name = "Carlos".to_string();
        x
    })
    .collect();

Alternatively, instead of cloning the whole persons Vec upfront, I'd instead use cloned() after filter() or clone() in map(), i.e. only when needed.

let person_found: Vec<Person> = persons
    .iter()
    .filter(|x| x.id == "3")
    .cloned()
    .map(|mut x| {
        x.name = "Carlos".to_string();
        x
    })
    .collect();

Upvotes: 5

Related Questions