yageek
yageek

Reputation: 4455

How to deal with multiple mutable borrows in Rust with match?

I'm trying to encapsulate an XML parser using quick-xml that takes some specific XML in entry. The main code is the following:

pub struct GPXParser<B: BufRead> {
    reader: Reader<B>,
    buff: Vec<u8>,
}

impl<B> GPXParser<B> {
    pub fn read_next<a>(&mut self) -> Result<XMLCache, Error> {
        match self.reader.read_event(&mut self.buff) {
            Ok(XMLEvent::Start(ref e)) if e.name() == b"wpt" => {
                self.read_next_wpt() // --> Multiple mutable borrows error
            }
            _ => Err(Error::NoCaches),
        }
    }

    fn read_next_wpt(&mut self) -> Result<XMLCache, Error> {
        match self.reader.read_event(&mut self.buff) {
            _ => Err(Error::NoCaches),
        }
    }
}

I then read this topic on rust-lang.org, mentioning that:

idiomatic Rust code typically avoids holding long-lived references to mutable object internals and favors alternate approaches that use immutable references or independent values

I tried to change my approach using an intermediate buff element:

pub struct GPXParser<B: BufRead> {
    reader: Reader<B>,
}

impl<B> GPXParser<B> {
    pub fn read_next<a>(&mut self) -> Result<XMLCache, Error> {
        let mut buff: Vec<u8> = Vec::new();

        match self.reader.read_event(&mut buff) {
            Ok(XMLEvent::Start(ref e)) if e.name() == b"wpt" => {
                self.read_next_wpt(&mut buff) // --> Multiple mutable borrows error
            }
            _ => Err(Error::NoCaches),
        }
    }

    fn read_next_wpt(&mut self, buff: &mut Vec<u8>) -> Result<XMLCache, Error> {
        match self.reader.read_event(buff) {
            _ => Err(Error::NoCaches),
        }
    }
}

I did not change the approach.

I tried to split the match statement in two lines:

pub fn read_next<a>(&mut self) -> Result<XMLCache, Error> {
    let result = self.reader.read_event(&mut self.buff);
    match result {
        Ok(XMLEvent::Start(ref e)) if e.name() == b"wpt" => self.read_next_wpt(),
        _ => Err(Error::NoCaches),
    }
}

But I get the same error:

error[E0499]: cannot borrow `*self` as mutable more than once at a time
  --> src/gpx/mod.rs:34:17
   |
31 |         let result = self.reader.read_event(&mut self.buff);
   |                                                  --------- first mutable borrow occurs here
...
34 |                 self.read_next_wpt()
   |                 ^^^^ second mutable borrow occurs here
...
39 |     }
   |     - first borrow ends here

Is there a way to use the same buffer all around the different methods calls?

Should some lifetimes come into the game?

If not, what would be an idiomatic Rust approach to solve this issue?

Upvotes: 4

Views: 2571

Answers (1)

Jmb
Jmb

Reputation: 23244

The problem comes from the 'b lifetime in the prototype for read_event:

pub fn read_event<'a, 'b>(
    &'a mut self, 
    buf: &'b mut Vec<u8>
) -> Result<Event<'b>>

This links the lifetime of the buffer with the lifetime of the return value so you can't re-use the buffer so long as the result is alive. You can get around it by returning early when you don't want to recurse and recursing only after you've finished using the result:

pub fn read_next<a>(&mut self) -> Result<XMLCache, Error> {
    match self.reader.read_event(&mut self.buff) {
        Ok(XMLEvent::Start(ref e)) if e.name() == b"wpt" => (),
        _ => { return Err(Error::NoCaches) },
    }
    self.read_next_wpt()
}

If you want to add more cases, you must first extract any relevant information you wish to use from the result, including any information required to choose the method to call, then have the result go out of scope before you call the method. For example:

pub fn read_next<a>(&mut self) -> Result<XMLCache, Error> {
    let next_method = match self.reader.read_event(&mut self.buff) {
        Ok(XMLEvent::Start(ref e)) if e.name() == b"wpt" => GPXParser<B>::read_next_wpt,
        Ok(XMLEvent::Start(ref e)) if e.name() == b"foo" => GPXParser<B>::read_next_foo,
        _ => { return Err(Error::NoCaches) },
    }
    next_method(self)
}

Or it might be easier to just use a different buffer each time if the performance hit is small enough to be acceptable (you should measure it to back up your decision).


Original answer below for reference:

Try to split the read_event and the match into separate lines:

let result = self.reader.read_event(&mut self.buff);
match result {
    ...
}

Your problem is that the buffer is borrowed mutably for the whole match expression, so you can't re-borrow the buffer inside the match arms. By splitting the code in two lines, the buffer is only borrowed for the first expression (let result=...) and can be borrowed again in the match.

This may be fixed in the future when non-lexical lifetimes become stable.

Upvotes: 1

Related Questions