2

I'm trying to use the retain(...) method to filter out values from a vector. As it stands, my code thus far is

let results = get_data(); // Returns Result<Vec<MyData>, Box<Error>>

match results {
    Ok(mut my_data) => {

        // Make sure that `name` is not None
        myData.retain(|ref d| d.name.is_some());

        // Only keep data if 'name' is in this list
        let names = ["Alice", "Bob", "Claire"];
        my_data.retain(|ref d| names.contains(d.name.unwrap().as_str()));
    },
    Err(_) => {}
}

However, this yields the error

|
|     my_data.retain(|ref d| names.contains(d.name.unwrap().as_str()));
|                                           ^^^^^^^^^^^^^^^^^^^^^^^^ expected &str, found str
|
= note: expected type `&&str`
           found type `&str`

It seems like the compiler is telling me two separate things here, but in both cases it seems to want an additional reference. However, when I try to change the relevant line to &names.contains(d.name.unwrap().as_str()), the compiler throws the following error.

|
|     my_data.retain(|ref d| names.contains(&d.name.unwrap().as_str()));
|                                            ^ cannot move out of borrowed content

How can I check whether a borrowed String (I think that's what the type of name.unwrap() is anyway) is contained in the names vector?

Jackson Petty
  • 161
  • 1
  • 10
  • Please include the error's context, so we know where it's pointing to. – Jorge Israel Peña May 29 '18 at 00:11
  • 1
    Please review how to create a [MCVE] and then [edit] your question to include it. We cannot tell what crates, types, traits, fields, etc. are present in the code. Ideally, produce something that reproduces your error on the [Rust Playground](https://play.rust-lang.org). Reduce your code as far as possible — do you *need* to return a `Result`; do you *need* a custom struct, do you *need* to have multiple `retain` statements, etc. Your code doesn't make sense: you say you have a `Result`, but your `match` statement uses `Result::Ok` and `Option::None`. – Shepmaster May 29 '18 at 01:09
  • 1
    Idiomatic Rust uses `snake_case` for variables, methods, macros, and fields and `UpperCamelCase` for types. Use `my_data` instead, please. – Shepmaster May 29 '18 at 01:13

1 Answers1

7

Here is a MCVE of your problem (your original code has other, unrelated problems that are ignored here):

struct MyData {
    name: Option<String>,
}

fn main() {
    let mut my_data: Vec<MyData> = vec![];

    // Only keep data if 'name' is in this list
    let names = ["Alice", "Bob", "Claire"];
    my_data.retain(|ref d| names.contains(d.name.unwrap().as_str()));
}
error[E0308]: mismatched types
  --> src/main.rs:10:43
   |
10 |     my_data.retain(|ref d| names.contains(d.name.unwrap().as_str()));
   |                                           ^^^^^^^^^^^^^^^^^^^^^^^^ expected `&str`, found `str`
   |
   = note: expected reference `&&str`
              found reference `&str`

As the error message states, slice::contains requires a reference to the type of value contained in the slice:

pub fn contains(&self, x: &T) -> bool
where
    T: PartialEq<T>,

Since your slice contains string literals (of type &'static str), it needs a &&str to compare against. We will take a reference to the result of the as_str method:

my_data.retain(|ref d| names.contains(&d.name.unwrap().as_str()));
//                                    ^-- added
error[E0507]: cannot move out of `d.name` which is behind a shared reference
  --> src/main.rs:10:44
   |
10 |     my_data.retain(|ref d| names.contains(&d.name.unwrap().as_str()));
   |                                            ^^^^^^
   |                                            |
   |                                            move occurs because `d.name` has type `std::option::Option<std::string::String>`, which does not implement the `Copy` trait
   |                                            help: consider borrowing the `Option`'s content: `d.name.as_ref()`

This error is already thoroughly explained elsewhere on Stack Overflow:

TL;DR — you don't own d, so you cannot unwrap d.name, consuming the value in the process. The fix is to use Option::as_ref:

my_data.retain(|ref d| names.contains(&d.name.as_ref().unwrap().as_str()));
//                                           ^^^^^^^^^-- added

However, needing to unwrap is ugly and it's ideally avoided. You can combine the two retain calls into one and skip the unwrap by using Option::map_or:

my_data.retain(|d| {
    d.name.as_ref().map_or(false, |name| {
        let name = name.as_str();
        names.contains(&name)
    })
});

This removes a potential panic and improves the efficiency of the code.

Since Rust 1.40, this can be shortened with Option::as_deref:

my_data.retain(|d| {
    d.name
        .as_deref()
        .map_or(false, |name| names.contains(&name))
});

See also:

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366