Cannot borrow as mutable more than once at a time in recursive function/loop

Hi. I need a bit of a help comprehending the error here

fn read_to_start<'a, R: BufRead>(reader: &mut Reader<R>,buf: &'a mut Vec<u8>,) -> Option<BytesStart<'a>> {
    match reader.read_event(buf).unwrap() {
        Event::Start(ev) | Event::Empty(ev) => return Some(ev),
        Event::Eof => return None,
        _ => ()
    }

    read_to_start(reader, buf)
}

I get cannot borrow `*buf` as mutable more than once at a time. Clearly it doesn't like the return Some(ev) since replacing it with () makes error go away. But why does it not like it? Clearly in that branch where mutable borrow of buf is retained - function terminates and the second mutable borrow is never reached, and in the branches where it is reached - the first reference isn't retained.

Originally this was the exact same code in a loop, but it also complained about buf being borrowed as mutable in the previous iteration, so i tried to go for recursion to see if it also results in an error.

Am i missing something obvious here?

This is a limitation of the current borrow checker. With the return Some(ev) is thinks the borrow contained in ev, that is the borrow of buf in match reader.read_event(buf).unwrap(), must last until the function return, and this includes the code after the match. Obviously this is false, the match either returns Some(ev) and thus the borrow or continues and there's no borrow that need to last until the function returns, but unfortunately the borrow checker doesn't see that. This will be hopefully fixed in the future with a new borrow checker called Polonius.

3 Likes

Hmm, yeah, this does seem to compile with Polonius, but i don't know if i can make rust-analyzer use it. Plus that does require using the nightly... Are there any workarounds for it in the stable?

In general, the workaround for these situations is to make sure that the borrow that you're going to return is not taken until you've made the decision of whether to return or continue.

In this case, that means you'll need to change how read_event() works, so that you can determine whether there's an event to be had (often called "peeking") separately from (I assume) borrowing that event out of the buffer.

2 Likes

Sadly, that's from someone else's crate. So i guess my only option it to take ownership each time.

Thank you both

Another possibility would be to write a read_to_start function that, instead of returning the BytesStart<'a> value, calls a callback with it. That way, the lifetimes of the borrows involved don't ever need to be extended to the function return.

2 Likes

It seems quite straightforward to do this on stable using @Yandros's crate polonius_the_crab - Rust

/*
[dependencies]
quick-xml = "0.23"
polonius-the-crab = "0.2"
*/

use quick_xml::{
    events::{BytesStart, Event},
    Reader,
};

use std::io::BufRead;

use polonius_the_crab::prelude::*;

fn read_to_start<'a, R: BufRead>(
    reader: &mut Reader<R>,
    mut buf: &'a mut Vec<u8>,
) -> Option<BytesStart<'a>> {
    polonius! {
        |buf| -> Option<BytesStart<'polonius>> {
            match reader.read_event(buf).unwrap() {
                Event::Start(ev) | Event::Empty(ev) => polonius_return!(Some(ev)),
                Event::Eof => polonius_return!(None),
                _ => (),
            }
        }
    }

    read_to_start(reader, buf)
}

(Rust Explorer)


Feel free to also post your loop-based version if I should try refactoring that one, too, to avoid the need for recursion.

Edit: On second thought, your description of what the loop-based version of the code should look like seems quite unambiguous, and something like

fn read_to_start<'a, R: BufRead>(
    reader: &mut Reader<R>,
    mut buf: &'a mut Vec<u8>,
) -> Option<BytesStart<'a>> {
    loop {
        polonius! {
            |buf| -> Option<BytesStart<'polonius>> {
                match reader.read_event(buf).unwrap() {
                    Event::Start(ev) | Event::Empty(ev) => polonius_return!(Some(ev)),
                    Event::Eof => polonius_return!(None),
                    _ => (),
                }
            }
        }
    }
}

or

fn read_to_start<'a, R: BufRead>(
    reader: &mut Reader<R>,
    mut buf: &'a mut Vec<u8>,
) -> Option<BytesStart<'a>> {
    polonius_loop! {
        |buf| -> Option<BytesStart<'polonius>> {
            match reader.read_event(buf).unwrap() {
                Event::Start(ev) | Event::Empty(ev) => polonius_return!(Some(ev)),
                Event::Eof => polonius_return!(None),
                _ => (),
            }
        }
    }
}

will then be about what you want.

3 Likes

Thank you!

I wasn't even aware something like that existed

It’s still fairly new (initial release ~3 months ago); and I hadn’t actually really looked into its API myself before today.

Pretty neat crate design, I must say; it uses just a single unsafe operation internally in order to extend some lifetime in a generic function that captures the required general pattern (source code); and as a soundness check, the same code without that unsafe lifetime extension compiles successfully on nightly with -Zpolonius enabled. The macros then are merely a convenience wrapper around that function.

1 Like