在文件链实现中绕过借用检查器

Bypass borrow checker in file chain implementation

提问人:Dr. Timofey Prodanov 提问时间:11/7/2023 最后编辑:John KugelmanDr. Timofey Prodanov 更新时间:11/8/2023 访问量:74

问:

我正在尝试实现一个简单的文件链。不幸的是,我的实现产生了借用检查器错误。BufRead::fill_buf

impl BufRead for FileChain {
    fn fill_buf(&mut self) -> io::Result<&[u8]> {
        loop {
            let buf = self.stream.fill_buf()?;
            if !buf.is_empty() {
                return Ok(buf);
            } if let Some(filename) = self.future_files.pop() {
                self.stream = BufReader::new(File::open(&filename)?);
            } else {
                return Ok(&[])
            }
        }
    }
}
error[E0506]: cannot assign to `self.stream` because it is borrowed
40 |     fn fill_buf(&mut self) -> io::Result<&[u8]> {
   |                 - let's call the lifetime of this reference `'1`
41 |         loop {
42 |             let buf = self.stream.fill_buf()?;
   |                       ---------------------- `self.stream` is borrowed here
43 |             if !buf.is_empty() {
44 |                 return Ok(buf);
   |                        ------- returning this value requires that `self.stream` is borrowed for `'1`
45 |             } if let Some(filename) = self.future_files.pop() {
46 |                 self.stream = BufReader::new(File::open(&filename)?);
   |                 ^^^^^^^^^^^ `self.stream` is assigned to here but it was already borrowed

您可以在此处找到完整的类定义(56 行)。

我认为这类似于已经知道的借款检查器问题,描述在这里,这里和这里 基本上,检查器认为引用块不会更改。但是,和更新不能同时发生。bufstreamreturn bufstream

现有的解决方案(例如从这里开始)不能在这里应用,因为我没有 HashMap/Vector,而且我受到 API 的限制。我也不能使用 Polonius,因为我只能使用稳定的功能。BufRead

我的问题是:是否可以使用安全代码在不牺牲速度的情况下修改此代码?如果这是不可能的,有没有办法使用不安全的代码覆盖借用检查器?

rust io 借用检查器

评论

1赞 user2722968 11/7/2023
如果用户调用函数一次,保留缓冲区,再次调用该函数,然后执行第二个代码路径并销毁已返回给调用方的缓冲区,会发生什么情况?
1赞 kmdreko 11/7/2023
@user2722968,编译器拒绝代码即可解决;返回的引用和再次调用它所需的可变引用将发生冲突。
0赞 Chayim Friedman 11/9/2023
波洛尼乌斯问题。编译器应接受此代码。

答:

3赞 user4815162342 11/7/2023 #1

有没有办法使用不安全的代码覆盖借用检查器?

虽然使用 unsafe 不会关闭借用检查器,但它确实允许您规避它的一些保护措施。例如,这编译并且我相信是合理的:

fn fill_buf(&mut self) -> io::Result<&[u8]> {
    loop {
        // unsafe: we either return a reference into self (which prevents
        // the caller from mutating self until dropping that reference),
        // or we proceed to mutate self. This is safe under borrowing
        // rules, but fails to compile due to limitations of the current
        // borrow checker. Here we use `transmute()` to temporarily
        // decouple the reference from self (it is reassigned the lifetime
        // of self on returning). Note that the safe code without
        // `transmute()` compiles under Polonius.
        unsafe {
            let buf = std::mem::transmute::<_, &'static [u8]>(self.stream.fill_buf()?);
            if !buf.is_empty() {
                return Ok(buf);
            }
        }
        if let Some(filename) = self.future_files.pop() {
            self.stream = BufReader::new(File::open(&filename)?);
        } else {
            return Ok(&[]);
        }
    }
}

使用 时,您需要自己证明代码实际上是合理的 - 上面的代码在 旁边的注释中记录了推理,这被认为是最佳实践。请记住,不安全会使您作弊得比安全得多。例如,修改签名以返回不正确的签名仍然编译,尽管它使函数明显不健全。这就是为什么在性能不是最重要的情况下最好避免使用这种方式的原因。unsafeunsafeResult<&'static [u8]>unsafe