是否可以编写一个调用 BufRead::fill_buf 的函数,直到中断错误不再发生而不使用不安全?

Is it possible to write a function that calls BufRead::fill_buf until an interrupted error no longer occurs without using unsafe?

是否可以编写一个调用 BufRead::fill_buf 的函数,直到 io::ErrorKind::Interrupted 不再出现而不使用 unsafe?

带有不安全代码的是:

use std::{
    io::{self, BufRead},
    slice,
};

fn fill_buf_and_ignore_interrupts(reader: &mut impl BufRead) -> io::Result<&[u8]> {
    let (buf_ptr, buf_len) = loop {
        match reader.fill_buf() {
            Ok(buf) => (buf.as_ptr(), buf.len()),
            Err(e) => {
                if e.kind() != io::ErrorKind::Interrupted {
                    return Err(e);
                }
            }
        }
    };
    Ok(unsafe { slice::from_raw_parts(buf_ptr, buf_len) })
}

如果我尝试 returnbreak Ok(buf),我会从借用检查程序中收到错误消息:

error[E0499]: cannot borrow `*reader` as mutable more than once at a time
 --> src/lib.rs:8:15
  |
6 | fn fill_buf_and_ignore_interrupts(reader: &mut impl BufRead) -> io::Result<&[u8]> {
  |                                           - let's call the lifetime of this reference `'1`
7 |     let (buf_ptr, buf_len) = loop {
8 |         match reader.fill_buf() {
  |               ^^^^^^ mutable borrow starts here in previous iteration of loop
9 |             Ok(buf) => return Ok(buf),
  |                               ------- returning this value requires that `*reader` is borrowed for `'1`

我尝试用递归替换循环,但错误仍然存​​在。我也尝试过使用#![feature(nll)],但它也不起作用。

你不能,也不应该出于下面突出显示的原因(它们与引用 return 或 unsafe 无关)。 fill_buf 并不像您认为的那样工作,这是由于文档中的一项重要警告:

Returns the contents of the internal buffer, filling it with more data from the inner reader if it is empty.

换句话说,如果特征正确实施并遵循文档中规定的合同,则在没有 consume 的情况下对 fill_buf 的后续调用将是空操作。因此,如果您在任何调用 fill_buf.

的任何地方忘记了该要求,那么只做一个而没有另一个会充满风险。

两种解决方案:

  • 如果您正在阅读流的末尾(这是 BufReader 在许多其他语言中所做的),只需 BufRead::read_to_end()BufRead::read_line()BufRead::read_until()。如果你能识别一个分隔符,read_until 可以很容易地变成一个可迭代的结构
  • 如果您尝试查看 数据并可能等待更多数据,则需要实现自己的特征。这不是经常需要的,因为大多数试图查看流的人都在寻找 PatternBufRead 涵盖的单个字节。

作为一个简短的总结,fill_buf 并没有按照您的想法行事,您不需要在不消耗缓冲区内部状态的情况下多次调用它。如果您不想这样做,那么 BufRead 的低级方法不是完成这项工作的工具。

我在这里找到了答案:

Rustc can't "deal" with conditional borrowing returns

所以,目前,在这种情况下,不安全是唯一的答案。

我认为今天在安全 Rust 中不可能循环到 return 对 fill_buf 的 return 的引用。 (至少我在放弃之前撞了一会儿脑袋。)

但是...您可以再打 fill_buf 一次。如果缓冲区已满,则调用应该 return Ok,并且我希望成本最低。 (也许内联和优化会完全消除它。)如果那不是真的,那么底层的 BufRead 实现是错误的。

我刚刚在跳过转义字节的 BufRead 适配器中做了类似的事情(当当前块没有更多的非转义字节可以传递时,在底层流的 fill_buf 上循环)。

use std::io::{self, BufRead};

pub fn fill_buf_and_ignore_interrupts(reader: &mut impl BufRead) -> io::Result<&[u8]> {
    while let Err(e) = reader.fill_buf() {
        if e.kind() != io::ErrorKind::Interrupted {
            return Err(e);
        }
    }
    reader.fill_buf()
}

不要注意另一个回答说你绝不能在没有 consume 的情况下调用 fill_buf。这是错的。您应该简单地期望,如果您再次调用 fill_buf 而不是先调用 consume,流将位于同一位置。您只需要在希望流前进时调用 consume 即可。 (而且我不知道你怎么会想到你应该在 fill_buf return 错误之后调用 consume 的荒谬想法...)