stringrustiteratorownershipfor-in-loop

Unable to bound lifetime in for each loop using str.lines() function


I'm new to rust and i'm building an implementation of the grep command to learn this language. I'm trying to create a function to match the search query in a case insensitive way, but i'm having trouble handling the lifetime of the value returned by str.lines() iterator in the for loop.

this is a minimal reproducible example containing the function i'm implementing:

use grep::{run, Config};
use std::{env::args, process};

pub struct Config {
    file_path: String,
    query: String,
}
fn main() {
    let contents: Config =Config {query: "Hello world!".to_owned(), file_path: "test".to_owned()};
    let matches = search_case_sensitive(&config, &contents);
    print_matches(&matches);
    Ok(config)
}

type Match<'a> = Vec<(&'a str, &'a str, &'a str)>;

// this function causes the compile error
// contents -> the full text content of the file i previously read
// config.query -> the string i'm searching for inside contents
fn search_case_insensitive<'a>(config: &Config, contents: &'a str) -> Match<'a> {
    let mut matches: Match<'a> = Vec::new();
    let query = config.query.to_lowercase();
    for line in contents.lines() {
        let indexes = line.to_lowercase().match_indices(&query);
        for (i, found) in indexes {
            let prev = &line[..i];
            let next = &line[i + found.len()..];
            matches.push((prev, found, next));
        }
    }
    matches
}

and i get this error when compiling:

error[E0716]: temporary value dropped while borrowed
  --> src/lib.rs:34:23
   |
34 |         let indexes = line.to_lowercase().match_indices(&query);
   |                       ^^^^^^^^^^^^^^^^^^^                      - temporary value is freed at the end of this statement
   |                       |
   |                       creates a temporary value which is freed while still in use
35 |         for (i, found) in indexes {
   |                           ------- borrow later used here
   |
   = note: consider using a `let` binding to create a longer lived value

from what I understand, line should be bounded to the 'a lifetime because it is a reference to every element of contents, but this is not happening, so line is not borrowed but moved and i does not live long enough to be used in indexes.

How can I instead borrow line from contents and bound it to the 'a lifetime?


Solution

  • The problem is that line.to_lowercase() is local to the function, but you're returning it.

    The easiest fix is to make an owned String:

    type Match<'a> = Vec<(&'a str, String, &'a str)>;
    
    // contents -> the full text content of the file i previously read
    // config.query -> the string i'm searching for inside contents
    fn search_case_insensitive<'a>(config: &Config, contents: &'a str) -> Match<'a> {
        let mut matches: Match<'a> = Vec::new();
        let query = config.query.to_lowercase();
        for line in contents.lines() {
            let line_lowercased = line.to_lowercase();
            let indexes = line_lowercased.match_indices(&query);
            for (i, found) in indexes {
                let prev = &line[..i];
                let next = &line[i + found.len()..];
                matches.push((prev, found.to_owned(), next));
            }
        }
        matches
    }
    

    However, note that your code is still incorrect. Specifically, to_lowercase() can change character indices, and even in general lowercasing two strings and comparing them is not enough for a case-insensitive comparison with respect to Unicode.