Reputation: 185
I am learning Rust by following the Rust Book and I am currently trying to modify the project in Chapter 12, but I can't understand why my code is not working.
The function in question is the search
function
fn search(query: &str, contents: String) -> Vec<String> {
contents.lines().filter(|line| line.contains(query)).collect()
}
which is supposed to get contents of a file as a string and return a collection of the lines in the file containing query
. In this form, it throws the error "a value of type std::vec::Vec<std::string::String>
cannot be built from an iterator over elements of type &str
".
I think that the error comes from the use of lines
since it doesn't take ownership of contents
. My question is if there is a better way to do this or if there is a similar method to lines
that does take ownership.
Upvotes: 2
Views: 1362
Reputation: 12207
As a Rust learner it is important to know the differences between strings (String) and string slices (&str), and how those two types interact.
The lines() method of an &str returns the lines as iterator over &str, which is the same type. However the lines method of a string also returns an iterator over &str, which is the same type as before but in this case not the same type as the input. This means, your output will be of type Vec<&str>. However in that case you need a lifetime because otherwise you can't return a reference. In this case your example would look like this:
fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
contents.lines().filter(|line| line.contains(query)).collect()
}
fn main() {
println!("found: {:?}",search("foo", "the foot\nof the\nfool"));
}
However if you want the vector to contain strings, you can use the to_owned()
function to convert a &str into a String:
fn search(query: &str, contents: &str) -> Vec<String> {
contents.lines().map(|line| line.to_owned()).filter(|line| line.contains(query)).collect()
}
fn main() {
println!("{:?}",search("foo", "the foot\nof the\nfool"));
}
However this is inefficient because some strings are created that aren't used so it is better to map last:
fn search(query: &str, contents: &str) -> Vec<String> {
contents.lines().filter(|line| line.contains(query)).map(|line| line.to_owned()).collect()
}
fn main() {
println!("{:?}",search("foo", "the foot\nof the\nfool"));
}
Or with contents
of type String, but I think this doesn't make much sense:
fn search(query: &str, contents: String) -> Vec<String> {
contents.lines().map(|line| line.to_owned()).filter(|line| line.contains(query)).collect()
}
fn main() {
println!("{:?}",search("foo", "the foot\nof the\nfool".to_owned()));
}
Explanation: Passing contents
as a String
isn't very useful because the search function will own it, but it is not mutable, so you can't change it to the search result, and also your search result is a vector, and you can't transform a single owned String into multiple owned ones.
P.S.: I'm also relatively new to Rust, so feel free to comment or edit my post if I missed something.
Upvotes: 4