在 Rust 中,我想从流或文件中读取以填充缓冲区,并检测 EOF

In Rust, I want to read from a stream or file to fill a buffer, and detect EOF

提问人:Thomas Mueller 提问时间:11/15/2023 最后编辑:Thomas Mueller 更新时间:11/16/2023 访问量:70

问:

在 Rust 中,我想(使用 BufReader)读取缓冲区数组来填充它。我想读取确切的字节数,但在 EOF 的情况下除外,在这种情况下,读取更少就可以了。我自己实现了一个帮助程序方法,如下所示,并且有效。但是,我想知道是否没有方便的方法已经做到了这一点。

pub fn read_fully<R: Read>(read: &mut R, buffer: &mut [u8]) -> Result<usize, Error> {
    let mut count: usize = 0;
    loop {
        let r = read.read(&mut buffer[count..])?;
        if r == 0 {
            return Ok(count);
        }
        count += r;
    }
}

对于我发现的每种现有方法,似乎都存在一个问题:

  • “read”可以少读(例如,对于 stdin)。
  • “read_exact”不适用于 EOF。
  • “read_to_end”需要一个 Vec 而不是一个数组,并且会读取所有内容,可能会扩展向量。

评论

3赞 Chayim Friedman 11/15/2023
read_to_end()不是你想要的。在文件结束之前,它将读取比所需字节更多的字节。我不认为您可以使用其他方法,您的自定义代码很好。
4赞 Chayim Friedman 11/15/2023
在这种情况下,您可能想做的一件事是忽略并重复读取。ErrorKind::Interrupted
0赞 Thomas Mueller 11/15/2023
是的,我尝试了 read_to_end() 并发现它可以读取更多字节......我改变了问题。感谢 ErrorKind::Interrupted 的提示:在阅读它时,似乎循环应该忽略这一点并继续。这使它变得更加复杂,但似乎是有道理的。
1赞 Aleksander Krauze 11/15/2023
快速笔记。您应该直接使用,而不是按照 API 指南使用R: Read&mut R
1赞 Chayim Friedman 11/15/2023
@ThomasMueller 将其标记为 ()。mutmut read: R

答:

0赞 Chayim Friedman 11/15/2023 #1

这里有一个想法,我不确定它是否比你的更好,但它也有效:

pub fn read_fully<R: Read>(read: &mut R, buffer: &mut [u8]) -> Result<usize, Error> {
    let mut data = Vec::with_capacity(buffer.len());
    read.take(buffer.len().try_into().unwrap())
        .read_to_end(&mut data)?;
    buffer[..data.len()].copy_from_slice(&data);
    Ok(data.len())
}

它的效率可能会低得多。是否更清楚由您决定。

评论

0赞 Thomas Mueller 11/15/2023
是的,我希望该方法是有效的(没有memcpy)。冗长很好。
0赞 Thomas Mueller 11/15/2023 #2

似乎应该忽略ErrorKind::Interrupted。因此,解决方案将变为:

pub fn read_fully<R: Read>(mut read: R, buffer: &mut [u8]) -> Result<usize, Error> {
    let mut count: usize = 0;
    loop {
        let r = read.read(&mut buffer[count..]);
        match r {
            Ok(0) => {
                return Ok(count)
            },
            Ok(x) => {
                count += x
            },
            Err(e) => {
                if e.kind() == ErrorKind::Interrupted {
                    // retry
                } else {
                    return Err(e);
                }
            }
        };
    }
}
1赞 Chayim Friedman 11/16/2023 #3

以下是使用实验性 API 的更短、更简洁的方法:

#![feature(read_buf, core_io_borrowed_buf)]

use std::io::{BorrowedBuf, Error, ErrorKind, Read};

pub fn read_fully<R: Read>(read: &mut R, buffer: &mut [u8]) -> Result<usize, Error> {
    let mut buffer = BorrowedBuf::from(buffer);
    match read.read_buf_exact(buffer.unfilled()) {
        Ok(()) => {}
        Err(err) if err.kind() == ErrorKind::UnexpectedEof => {}
        Err(err) => return Err(err),
    }
    Ok(buffer.len())
}

此时,您可能只想将 a 作为参数:BorrowedCursor

#![feature(read_buf, core_io_borrowed_buf)]

use std::io::{BorrowedCursor, Error, ErrorKind, Read};

pub fn read_fully<R: Read>(read: &mut R, buffer: BorrowedCursor<'_>) -> Result<(), Error> {
    match read.read_buf_exact(buffer) {
        Err(err) if err.kind() == ErrorKind::UnexpectedEof => Ok(()),
        v => v,
    }
}

这允许使用未初始化的缓冲区等简洁操作。

评论

0赞 Thomas Mueller 11/17/2023
谢谢,有趣!一旦它不再是实验性的,我就会使用它