'search_case_insensitive' from Ch 12 in the book using existing 'search'

Hi, I am trying to implement myself 'search_case_insensitive' and I didn't manage to make it compile.
I have implemented 'search' and trying to reuse the function but I keep having issues.

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

pub fn search_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
    let query_lowercase: &str = &query.to_lowercase();
    let contents_lowercase: &str = &contents.to_lowercase();
    search(query_lowercase, contents_lowercase)
}

These are my implementations and the error I get is

error[E0515]: cannot return value referencing temporary value
  --> src/lib.rs:23:5
   |
22 |     let contents_lowercase: &str = &contents.to_lowercase();
   |                                     ----------------------- temporary value created here
23 |     search(query_lowercase, contents_lowercase)
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ returns a value referencing data owned by the current function

I understand why the value is indeed temporary, but I have failed to make it not so.
What am I missing?

  1. You can only borrow (take reference to) something that is already stored (owned) somewhere. References are not merely pointers, they are all about ownership.

  2. Variables in Rust are semantically meaningful. foo().bar() is different from let tmp = foo(); tmp.bar().

to_lowercase() returns a new object, which has to be stored somewhere. If you don't assign it to a variable, the compiler will give it temporary storage valid only for that one expression. So taking a reference to an object that will be automatically destroyed immediately after you take the reference doesn't make sense, because it's a use-after-free bug.

&str itself is not a string, but a temporary view of a String (or Box<str> or similar) stored somewhere else.

This should solve it:

let tmp = query.to_lowercase();
let query_lowercase: &str = &tmp;

You can also write:

search(&query.to_lowercase(), &contents.to_lowercase())

because in this special case everything is in one expression, and the temporary scope of to_lowercase()'s results happens to be just long enough for the search() call.

Thanks for the help, I was surprised to learn that variables have a semantic meaning.
However, both solutions don't seem to work for me, they give me the same error about temporary value

    Checking minigrep v1.38.0 (/home/bremys/Projects/RustProjects/minigrep)
error[E0515]: cannot return value referencing temporary value
  --> src/lib.rs:26:5
   |
26 |     search(&query.to_lowercase(), &contents.to_lowercase())
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-----------------------^
   |     |                              |
   |     |                              temporary value created here
   |     returns a value referencing data owned by the current function

and it actually makes sense to me, contents.to_lowercase() will drop anyway at the end of the function while I think I want it to drop it along with contents - I am just not sure if/how that is possible.

Ahh, sorry, I didn't pay attention to the fact that search returns values from its arguments. In that case you need to change borrowing Vec<&str> to an owning Vec<String> and return that.

search(…)
   .into_iter().map(|s| s.to_owned()).collect()

This is because contents_lowercase is created inside the function, and will be destroyed inside the function. So you can't return an array of temporary string views pointing to inside of contents_lowercase and use it after that variable is gone.

The problem can be simplified to:

fn search(contents: &str) -> &str {
   contents // works
}
fn search(contents: &str) -> &str {
   // can't possibly work: would cause use-after-free crash
   contents.to_lowercase() 
}
fn search(contents: &str) -> String {
   contents.to_lowercase() // works
}

It would also work if you returned lines of the original, non-lowercased content. Then your result wouldn't point to a temporary lowercased data. You'd need something like:

content_lowercase.lines().zip(content_original.lines())
  .filter(|(lower, _)| …)
  .map(|(_, orig)| orig)
  .collect()
1 Like