1

I am trying to figure out how to continue iterating through a file from the current line I am on without having to start from the beginning. I am using BufReader to accomplish the line by line iteration. Here is an example of what I am trying to do:

use std::{
    fs::File,
    io::{self, BufRead, BufReader},
};

fn main() -> io::Result<()> {
    let chapter = "Chapter 1";
    let sentence = "Sentence 1";

    let file = File::open("document.txt")?;
    let reader = BufReader::new(file);

    for line in reader.lines() {
        if chapter == line.unwrap() {
            for line in reader.lines() {
                if sentence == line.unwrap() {
                    println!("Found the reference!");
                }
            }
        }
    }

    Ok(())
}

I cannot iterate like this due to the error:

error[E0382]: use of moved value: `reader`
    --> src/main.rs:15:25
     |
11   |     let reader = BufReader::new(file);
     |         ------ move occurs because `reader` has type `std::io::BufReader<std::fs::File>`, which does not implement the `Copy` trait
12   | 
13   |     for line in reader.lines() {
     |                        ------- `reader` moved due to this method call
14   |         if chapter == line.unwrap() {
15   |             for line in reader.lines() {
     |                         ^^^^^^ value moved here, in previous iteration of loop
     |
note: this function consumes the receiver `self` by taking ownership of it, which moves `reader`

Is there a way to continue line iteration using the same BufReader?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
J Dub
  • 25
  • 4

2 Answers2

2

I think in all cases you'll have to desugar the iteration because you need more control than what the built-in for would allow. I see two possibilities here:

One, simply desugar to a while let and use the same iterator in both loops:

while let Some(line) = lines.next() {
    if chapter == line.unwrap() {
        while let Some(line) = lines.next() {
              if sentence == line.unwrap() {
                     println!("Found the reference!");
              }
        }
    }
}

this means the "inner" loop will start where the "outer" loop left off, but the "outer" loop will resume where the "inner" stopped.

If this is undesirable, you can "fork" the iterators using Itertools::tee, however I expect this would require boxing your iterators as you'd be stacking up Tees. Which means accumulating a stack of dynamically dispatched iterators.

tee also requires that the Item be clonable (another cost), which means you'd have to unwrap your values before iterating:

let mut lines: Box<dyn Iterator<Item=_>> = Box::new(BufReader::new(file).lines().map(|l| l.unwrap()));

while let Some(line) = lines.next() {
    let (a, sub) = lines.tee();
    lines = Box::new(a);
    if chapter == line {
        for line in sub {
              if sentence == line {
                     println!("Found the reference!");
              }
        }
    }
}
Masklinn
  • 34,759
  • 3
  • 38
  • 57
0

Iterate over the iterator by reference:

let mut lines = reader.lines();

for line in &mut lines {
    if chapter == line.unwrap() {
        break;
    }
}

for line in lines {
    if sentence == line.unwrap() {
        println!("Found the reference!");
    }
}

You may need to wrap it in a loop and handle what happens when things are not found:

let mut lines = reader.lines();

loop {
    let mut found = false;

    for line in &mut lines {
        if chapter == line.unwrap() {
            found = true;
            break;
        }
    }

    if !found {
        break;
    }

    for line in &mut lines {
        if sentence == line.unwrap() {
            println!("Found the reference!");
        }
    }
}

See also:

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366