是否可以在stdin.lines()中间执行另一次读取?

时间:2018-06-16 17:56:42

标签: rust borrow-checker

请考虑以下代码:

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

fn main() {
    let mut stdin = io::stdin();

    let mut content_length = 0;
    for line_wrapped in stdin.lock().lines() {
        let line = line_wrapped.unwrap();
        if line == "" {
            let mut buf = vec![0u8; content_length];
            stdin.read_exact(&mut buf).unwrap();
            print!("{:?}", buf);
        }
        if line.starts_with("Content-Length: ") {
            content_length = line
                .split("Content-Length: ")
                .nth(1)
                .unwrap()
                .parse()
                .unwrap();
        }
    }
}

编译器输出:

error[E0502]: cannot borrow `stdin` as mutable because it is also borrowed as immutable
  --> src/main.rs:11:13
   |
7  |     for line_wrapped in stdin.lock().lines() {
   |                         ----- immutable borrow occurs here
...
11 |             stdin.read_exact(&mut buf).unwrap();
   |             ^^^^^ mutable borrow occurs here
...
22 |     }
   |     - immutable borrow ends here

有没有办法在保持程序的类似结构的同时修复错误(在.lines()内读取)?

1 个答案:

答案 0 :(得分:2)

在相同流的缓冲和非缓冲读取之间交替可能非常棘手。如果您没有lock标准输入来调用lines(),则用于实现StdinLock的内部缓冲区可能会超出空行的\n,并且随后的read_exact电话不会在正确的地方开始。

所以你只需要lock一次,你必须在给你read_exact的同一个缓冲读卡器上调用Lines,以确保没有字节丢失。乍一看,这看起来不可能:lines()按值self获取,因此一旦调用它,就无法在同一个对象上调用read_exact。但是你可以使用一些技巧。

BufRead contains此一揽子impl的文档:

impl<'a, B: BufRead + ?Sized> BufRead for &'a mut B

&mut引用了实现BufRead 的内容实现BufRead。因此,您可以对&mut进行临时StdinLock引用,在上致电lines() ,及时将Lines丢弃至read_exact将有效负载转换为buf,然后使用另一个&mut引用和另一个Lines重新开始。

这种方法需要添加另一个循环,使用标志has_header来指示是否打破外循环。它不是很漂亮,但也许你可以使用它。

let stdin = io::stdin();
let mut stdin_buf = stdin.lock();

'record: loop {
    let mut content_length = 0;
    let mut has_header = false;
    'header: for line_wrapped in (&mut stdin_buf).lines() {
        let line = line_wrapped.unwrap();
        if line.starts_with("Content-Length: ") {
            content_length = line["Content-Length: ".len()..].parse().unwrap();
        }
        if line.is_empty() {
            has_header = true;
            break 'header;
        }
    }
    if has_header {
        let mut buf = vec![0u8; content_length];
        stdin_buf.read_exact(&mut buf).unwrap();
        println!("{:?}", buf);
    } else {
        break 'record;
    }
}

最后一点:当Content-Length标题不存在时,不清楚会发生什么。如果您的原始代码有效,它将重用先前定义的值(无论最后的内容长度是多少,或者对于第一个记录是0)。我的版本一直使用0。在Rust中使用Option来表示可能未初始化的值(例如content_length)是惯用的,因此如果缺少None标题,则将其初始化为Content-Length可能是更好的选择是一个需要检测的错误。