Iterator with multiple lifetimes

Viewed 57

Consider the following method:

fn search<'a>(query: &str, text: &'a str) -> Vec<&'a str> {
    let mut matches = Vec::new();
    for line in text.lines() {
        if line.contains(query) {
            matches.push(line);
        }
    }
    matches
}

I want to refactor this to return a lazy iterator instead:

fn search<'a>(query: &str, text: &'a str) -> impl Iterator<Item=&'a str> {
    text.lines().filter(|&line| line.contains(query))
}

However, this does not compile because now the iterator needs to live not only as long as text, but query as well. Adding an explicit lifetime to query should do the trick:

fn search<'a>(query: &'a str, text: &'a str) -> impl Iterator<Item=&'a str> {
   text.lines().filter(|&line| line.contains(query))
}

Unfortunately, this still does not compile:

error[E0597]: `query` does not live long enough
  --> src\lib.rs:32:39
   |
29 | fn search<'a>(query: &'a str, text: &'a str) -> impl Iterator<Item=&'a str> {
   |           -- lifetime `'a` defined here         --------------------------- opaque type requires that `query` is borrowed for `'a`
...
32 |         .filter(|&line| line.contains(query))
   |                 -------               ^^^^^ borrowed value does not live long enough
   |                 |
   |                 value captured here
...
35 | }
   | - `query` dropped here while still borrowed

Any clue of what's going on here?

1 Answers

Closures capture variables by reference unless the variable is consumed (moved) from within the closure.

Here, your closure captures query by reference, which means it stores a reference to a reference. &'c &'b str explicitly implements Pattern<'a>, so the closure doesn't need ownership of query.

Now, the closure is capturing a reference to a local variable, and the function is trying to return ownership of that closure.

The fix is simple: add move to the closure, so that a copy of query is captured instead.

fn search<'a>(query: &'a str, text: &'a str) -> impl Iterator<Item=&'a str> {
   text.lines().filter(move |&line| line.contains(query))
}
Related