why should this be borrowed?

Viewed 117

From The Book, listing 13-29:

pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
    contents
        .lines()
        .filter(|line| line.contains(query))
        .collect()
}

After an explanation of the above, the reader is invited, "Feel free to make the same change to use iterator methods in the search_case_insensitive function as well." Don't mind if I do:

pub fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
    contents
        .lines()
        .filter(|line| line.to_lowercase().contains(query.to_lowercase()))
        .collect()
}

Nope:

error[E0277]: expected a `FnMut<(char,)>` closure, found `String`
   --> src/lib.rs:59:53
    |
59  |         .filter(|line| line.to_lowercase().contains(query.to_lowercase()))
    |                                            -------- ^^^^^^^^^^^^^^^^^^^^ expected an implementor of trait `Pattern<'_>`
    |                                            |
    |                                            required by a bound introduced by this call
    |
    = note: the trait bound `String: Pattern<'_>` is not satisfied
    = note: required because of the requirements on the impl of `Pattern<'_>` for `String`
note: required by a bound in `core::str::<impl str>::contains`
help: consider borrowing here
    |
59  |         .filter(|line| line.to_lowercase().contains(&query.to_lowercase()))
    |                                                     +

Indeed, this compiles and passes the tests from the earlier examples:

pub fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
    contents
        .lines()
        .filter(|line| line.to_lowercase().contains(&query.to_lowercase()))
        .collect()
}

For that matter, search will compile and pass if query is borrowed:

pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
    contents
        .lines()
        .filter(|line| line.contains(&query))
        .collect()
}

So why must query be borrowed in search_case_insensitive but its being a reference (or not) makes no difference to search?

2 Answers

The method str::contains() takes as a predicate generic P: Pattern. The Pattern trait is implemented for char, &char, &String, &[char], &str, &&str, [char; N], &[char; N] and F where F: FnMut(char) -> bool. All of those can be passed to contains(). However, it is not implemented for String. So in search(), you pass &str. That's fine. If you borrow it, you pass &&str, which is also fine. But in search_case_insensitive(), if you borrow you pass &String and that's fine, but if you don't, you pass String which does not implement the Pattern trait.

The contains() method expects as input some type which implements the Pattern trait. This trait is implemented for &str, but not String. This means that contains() can be used with an &str, but not a String (at least not directly).

For the line.contains(query) case, query is already an &str, so we're good.

query.to_lowercase() returns a String type, so it does not compile. However, String can be turned into an &str by borrowing, which is why &query.to_lowercase() works.

Related