是否可以在不逐行缓存输入的情况下从 io::stdin() 中读取字符?

Is it possible to read characters from `io::stdin()` without caching input line-by-line?

此题参考Rust稳定版1.2.0

我只想遍历 CLI 应用程序标准输入中的字符。完全有可能将 stdinread_line 方法读取到临时 String 实例中,然后迭代它的 chars() 迭代器。

但我不喜欢这种方法,因为它分配了一个完全不必要的 String 对象。 Stdin trait 的文档实现了 Read trait,which has chars() 迭代器,但它被标记为不稳定(因此不能与稳定的编译器版本一起使用)。

是否有另一种不太明显的方法来逐个字符地读取标准输入,而无需任何额外的 Rust 端缓冲?

您可以通过使用单字节数组并继续读取直到 Result 变成 Err 来完成此操作。但是,这有一个问题,因为如果您不阅读 ASCII 字符,就会出现这种情况。如果您要解决这个问题,最好只分配一个 String,并使用 chars 迭代器,因为它可以处理这个问题。

示例代码:

use std::io::{stdin, Read};

fn main() {
    loop {
        let mut character = [0];
        while let Ok(_) = stdin().read(&mut character) {
            println!("CHAR {:?}", character[0] as char);
        }
    }
}

示例输出:

Hello World
CHAR Some('H')
CHAR Some('e')
CHAR Some('l')
CHAR Some('l')
CHAR Some('o')
CHAR Some(' ')
CHAR Some('W')
CHAR Some('o')
CHAR Some('r')
CHAR Some('l')
CHAR Some('d')
CHAR Some('\n')
你好世界
CHAR Some('\u{e4}')
CHAR Some('\u{bd}')
CHAR Some('\u{a0}')
CHAR Some('\u{e5}')
CHAR Some('\u{a5}')
CHAR Some('\u{bd}')
CHAR Some('\u{e4}')
CHAR Some('\u{b8}')
CHAR Some('\u{96}')
CHAR Some('\u{e7}')
CHAR Some('\u{95}')
CHAR Some('\u{8c}')
CHAR Some('\n')

对于您可能关心的情况是正确的,ASCII 字符。我想按照你的措辞来解决这个问题:

I just want to iterate over the characters in the standard input of my CLI application.

在 Rust 中,char 是一种 32 位(4 字节)类型,表示 Unicode 代码点。但是,IO 抽象操作是在字节级别上进行的。您需要使用 一些 类型的编码,将代码点映射到字节序列,目前 war 的赢家是 UTF-8。

UTF-8 将使用 maximum of 4 bytes 来表示单个代码点,但采用与本机不同的位模式。要正确地逐个字符地阅读,您将总是需要某种缓冲区。

还有一个问题是,缓冲区末尾的部分字符需要移回到缓冲区的开头,这是相对昂贵的。最好的解决方案是分摊许多字符的成本,因此读取更大的块可以更快。

对于那些来自未来的人,我们有 fn bytes(self) -> Bytes<Self>](link) in std::io::Read , which allows us to reimplement 以更优雅的方式。

use std::io::{Read, stdin};

fn main() {
    for c in stdin().bytes().map(|x| x.expect("cannot read char from stdin")) {
        println!("CHAR {}", char::from(c));
    }
}