Stepping through an iterator without getting "borrow of moved value"

I'm trying to parse a file by stepping through sections of the file, one at a time. I have an iterator on the lines of the file, and the process I'm trying to use is as follows:

  1. Search for the header of a block.
  2. Loop through lines in the block, processing them.
  3. Repeat until the search fails.

This isn't too hard to translate into Rust:

fn main() {
    let script = include_str!("../ex.py");
    let mut line_it = script.lines();
    while let Some(header) = line_it.find(|s| s.starts_with("# /// ")) {
        println!("HEADER: {:?}", header);
        for line in line_it.take_while(|s| *s != "# ///") {
            println!("CONTENT: {} {:?}", &line.len(), &line);
        }
    }
}

But the borrow checker objects because find borrows line_it, which was moved into take_while in the previous iteration of the loop. I think I can see why this is happening, but I don't understand it well enough to work out how to rework it to do what I want.

Can anyone help clarify for me and/or explain how I can fix this? Thanks.

Use by_ref() to borrow the iterator for any methods that require transfer of ownership.

for line in line_it.by_ref().take_while(|s| *s != "# ///") {
1 Like

Wow, really? It's that simple? (Goes away and checks.) Yes, it is! Thanks, that's awesome.

I'm glad it's that simple, but I have to say I really don't understand why that works. I looked at the source of by_ref and it doesn't do anything but return its self. I assume the magic is in the types, but I'm not 100% clear why. Is it the fact that by_ref takes &mut self, and so the iterator gets coerced to a mutable reference to itself, which can then be safely dropped because ownership stays with the original variable?

I thought I'd tried (&mut line_it).take_while(), and got some error that I didn't understand. But I just tried it again, and now it works - presumably because it's the inline version of by_ref.

I'm not sure if I'm more confused or less, at this point. But my code now works, which is the most important thing for now :slightly_smiling_face: Thanks again.

That is the inline version of by_ref, indeed.

The key is that if I: Iterator, then &mut I: Iterator as well -- and the implementation just forwards on the next method for example. So you can wrap &mut line_it with iterator adapters (the constructors of which take an iterator by value) without giving away line_it itself.


Similar patterns come up elsewhere: If you run into some

fn foo<W: Write>(writer: Write)

and don't want to give away your File or whatever, you can pass in &mut file because there's an implementation of Write for &mut W where W: Write.

3 Likes

For full clarity, there is an explicit implementation that makes the re-borrow work.

Those re-borrow impls can be kind of difficult to notice, which makes discoverability a challenge. Once you know of them, it's easy to resolve or workaround ownership problems in existing trait APIs. I don't know what to do to make documentation better. I try to look for trait implementations on &T or &mut T when I need to do this kind of type juggling.

One of the most surprising impls IMHO is impl Read for &File. It's surprising because Read::read() receives &mut self! In other words, &mut &File is readable. You don't need exclusive access to the file handle to read from it, regardless of what the Read trait seems to indicate.

4 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.