如何在Rust中使用match处理多个可变借用的问题?

4

我正在尝试使用quick-xml来封装一个XML解析器,输入的是一些特定的XML内容。主要代码如下:

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),
        }
    }
}

我读了这个关于rust-lang.org的主题,其中提到:

习惯用法的rust代码通常避免持有长期的对可变对象内部的引用,并支持使用不可变引用或独立值的替代方法。

我尝试改变我的方法,使用一个中间的buff元素:

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),
        }
    }
}

我没有改变方法。

我尝试将match语句分成两行:

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),
    }
}

但是我得到了同样的错误:
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

有没有一种方法可以在不同的方法调用中使用相同的缓冲区?

是否需要引入某些生命周期?

如果不需要,那么解决这个问题的惯用Rust方法是什么?


为什么要使用 XMLEvent::Start(ref e)?而不是 XMLEvent::Start(e) - Boiethios
我只是简单地查看了存储库https://github.com/tafia/quick-xml上的自述文件。这有什么影响吗?两者之间有什么区别? - yageek
如果删除 ref,它还能编译吗?如果可以,我会写一个答案。 - Boiethios
不好意思,我又遇到了一个错误:“无法将按移动方式绑定到模式保护”,并且指向符号“e”,并解释说“将值移入模式保护”。 - yageek
1个回答

1
问题来自于读取事件的原型'b生命周期的部分:
pub fn read_event<'a, 'b>(
    &'a mut self, 
    buf: &'b mut Vec<u8>
) -> Result<Event<'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" => (),
        _ => { return Err(Error::NoCaches) },
    }
    self.read_next_wpt()
}

如果您想添加更多的案例,您必须首先从结果中提取任何相关信息,包括选择要调用的方法所需的任何信息,然后在调用该方法之前使结果超出范围。例如:
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)
}

如果性能损失可以接受,那么每次使用不同的缓冲区可能更容易(您应该测量它以支持您的决定)。


以下是供参考的原始答案:

尝试将read_event和匹配分成单独的行:

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

你的问题在于缓冲区在整个匹配表达式中被可变地借用,因此你不能在匹配分支中重新借用缓冲区。通过将代码拆分为两行,缓冲区仅在第一个表达式(let result=...)中被借用,并且可以在匹配中再次借用。

当非词法生命周期稳定后,这个问题可能会得到解决。


我总是在将语句拆分为两行时得到相同的错误 :( - yageek
那么你能给出完整的错误信息吗?特别是编译器应该指出可变借用发生的确切位置。 - Jmb
它出现的方式与以前完全相同,在调用第二种方法 self.read_next_wpt() 时。 - yageek
请问您能提供完整的信息吗? - Jmb
新的答案很好,但如果我添加更多调用其他方法的情况怎么办? - yageek

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接