Search code examples
rustclosuresborrow-checkerownershipborrowing

Rust - Converting from `&str` to `String` and back with Closures


I'm working through the book and I'm not understanding why this function doesn't compile:

pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
    contents
        .lines()                           // Fetch an iterator for each line in `contents`
        .map(|x| x.to_lowercase())         // (x is now String) Convert each line to lowercase
        .filter(|x| x.contains(query))     // Filter out lines that do not contain query
        .map(|x| x.trim())                 // Eliminate extra whitespace
        .collect()                         // Consume iterator and produce Vec<&str>
}

Without the to_lowercase() line it will run, and I'm guessing that is because that will return a String instead of the &str we'll need to output at the end. However when I either substitute a conversion back to &str like:

// -- snip --
.map(|x| x.to_lowercase().to_str())
// -- snip --

This states that a temporary value is being referenced. Which I assume because &str reference the String, when the String is released it makes my &str invalid as well.

Are closures just not a good way of handling this, and I should break it into different statement?


Solution

  • This states that a temporary value is being referenced. Which I assume because &str reference the String, when the String is released it makes my &str invalid as well.

    This assumption is correct.

    Are closures just not a good way of handling this, and I should break it into different statement?

    No amount of refactoring that function will change the fact that to_lowercase() requires modifying the &str and has to produce a String, so if lowercasing the contents is a requirement then this is the best you can do:

    fn search(query: &str, contents: &str) -> Vec<String> {
        contents
            .lines()                           // Fetch an iterator for each line in contents
            .map(|x| x.trim().to_lowercase())  // Trim & lowercase string
            .filter(|x| x.contains(query))     // Filter out lines that do not contain query
            .collect()                         // Consume iterator and produce Vec<String>
    }
    

    If you want to perform case-insensitive filtering but still return the unmodified contents (no lowercasing) then you can do this:

    fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
        contents
            .lines()                                        // Fetch an iterator for each line in contents
            .filter(|x| x.to_lowercase().contains(query))   // Filter out lines that do not contain query
            .map(|x| x.trim())                              // Trim whitesapce
            .collect()                                      // Consume iterator and produce Vec<&'a str>
    }