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:
&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.
You can address the inability to compare &String
to String
by borrowing the latter, ie changing n == some_name
to n == &some_name
.
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>
.
...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.