0

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.

14159
  • 125
  • 5
  • 2
    Add `.map (String::from)` before the `.collect()`: [playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=303e4a9535db35686d19052de8e90be1) – Jmb Sep 09 '22 at 11:53
  • If you actually want to return an iterator owning `content`, you can also use the techniques described in [Is there an owned version of String::chars?](https://stackoverflow.com/questions/47193584/is-there-an-owned-version-of-stringchars) and [How can I store a Chars iterator in the same struct as the String it is iterating on?](https://stackoverflow.com/questions/43952104/how-can-i-store-a-chars-iterator-in-the-same-struct-as-the-string-it-is-iteratin) – Sven Marnach Sep 09 '22 at 12:06
  • "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." there is not because it doesn't really make sense: usually the point of taking ownership is to be more efficient e.g. by reusing an existing allocation. But with an unspecified allocator you can't generically "split" a big allocation into lots of small ones, so there is no way for Rust to generically reuse `contents`' buffer for the `lines`. Therefore there's no point to an owning iterator, you can just `map` to a `str->String` conversion as Jmb shows. – Masklinn Sep 09 '22 at 13:17
  • "Therefore there's no point to an owning iterator" (except for borrinwing-related issues) – Masklinn Sep 09 '22 at 13:19

1 Answers1

3

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.

Konrad Höffner
  • 11,100
  • 16
  • 60
  • 118