简体   繁体   中英

Checking for values in Vectors of strings in Rust

I'm just getting started with Rust and have hit upon an issue where I'm getting a bit lost in types, pointers, borrowing and ownership.

I have a vector of Person structs, each of which has a name property. I'd like to collect all the names into another vector, and then check if that list contains a given string.

This is my code:

struct Person {
    name: String,
}

fn main() {
    let people = vec![Person {
        name: String::from("jack"),
    }];
    
    let names: Vec<_> = people.iter().map(|p| p.name).collect();

    let some_name = String::from("bob");
    if names.iter().any(|n| n == some_name) {
    }
}

This fails to compile:

error[E0277]: can't compare `&String` with `String`
  --> src/main.rs:13:31
   |
13 |     if names.iter().any(|n| n == some_name) {
   |                               ^^ no implementation for `&String == String`
   |
   = help: the trait `PartialEq<String>` is not implemented for `&String`

I think names should have the type Vec<String> , so I'm a bit lost as to where the &String comes from. Is this a case of me holding it wrong? I'm most familiar with JavaScript so I'm trying to apply those patterns, and may be not doing it in the most "rusty" way!

The type of p is not Person but &Person , ie a reference to a value. In JavaScript there are no structs. The closest alternative is an object and JS objects are always stored in the heap and are accessed via references (although that's less explicit). In Rust you really need to think more about the memory layout of your data. That can complicate parts of your code, but it also gives you power and allows for better performance.

Here is a correct version of your code:

struct Person {
    name: String,
}

fn main() {
    let people = vec![Person {
        name: String::from("jack"),
    }];
    
    let names: Vec<_> = people.iter().map(|p| &p.name).cloned().collect();
    

    let some_name = String::from("bob");
    if names.iter().any(|n| n == &some_name) {
    }
}

Notice that in n == &some_name we are now comparing a &String with a &String .

Also note I added .cloned() , so that you avoid another error, related to moving the name strings.

A slightly more optimized version would be to only collect the references to the names, like this:

    let names: Vec<_> = people.iter().map(|p| &p.name).collect();   
    let some_name = String::from("bob");
    if names.iter().any(|n| *n == &some_name) {
    }

Note that in this version n is of type &&String , so we dereference it once via *n . That way we are again comparing a &String with a &String . Rust also allows you to write something like this: |&n| n == &some_name |&n| n == &some_name , which is practically the same.

Several things going on there:

  1. &String comes from the fact that iter() iterates over the collection without consuming it, and therefore can only give you references into items of the collection.

  2. You can address the inability to compare &String to String by borrowing the latter, ie changing n == some_name to n == &some_name .

  3. After you do that, the compiler will tell you that p.name attempts to move name out of the reference p . To get rid of it, you can change p.name to &p.name , thus creating a Vec<&String> .

  4. ...after which n will become &&String (,), so you'll need n == &&some_name . The example then compiles.

Because Rust does not impl PartialEq<&String> for String by default, here are impl PartialEq<&String> for &String and impl PartialEq<String> for String .

Here the type of n is &String , the type of some_name is String , so you need to balance the references on the left and right side of == .

Both of the following fixes work. I prefer the first one, because the second one has a dereference which implies extra overhead for me. I'm not sure if Rust explicitly defines that there won't be overhead here, and even if there is, LLVM can usually optimize it.

if names.iter().any(|n| n == &some_name) // &String == &String
// or
if names.iter().any(|n| *n == some_name) // String == String

Then you will encounter another error:

error[E0507]: cannot move out of `p.name` which is behind a shared reference
  --> src/main.rs:10:47
   |
10 |     let names: Vec<_> = people.iter().map(|p| p.name).collect();
   |                                               ^^^^^^ move occurs because `p.name` has type `String`, which does not implement the `Copy` trait

For more information about this error, try `rustc --explain E0507`.

|p| p.name |p| p.name tries to move the name out of p , which is not necessary for the later checks, a &str is enough here. Returns the &str of a String via String::as_str : |p| p.name.as_str() |p| p.name.as_str()

Because of this fix, the type of n below becomes &&str , you can use names.into_iter() to get an iterator for ownership of names .

Fixed code:

struct Person {
    name: String,
}

fn main() {
    let people = vec![Person {
        name: String::from("jack"),
    }];
    
    let names: Vec<_> = people.iter().map(|p| p.name.as_str()).collect();

    let some_name = String::from("bob");
    if names.into_iter().any(|n| n == some_name) {
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM