Reputation: 11618
Consider the following code:
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();
}
}
}
And the compiler output:
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
Is there a way I could fix the error while keeping a similar structure of the program (read within a .lines()
)?
Upvotes: 2
Views: 129
Reputation: 28075
Alternating between buffered and non-buffered reads of the same stream can be quite tricky. If you didn't have to lock
standard input in order to call lines()
, the internal buffer used to implement StdinLock
could consume beyond the \n
of the empty line, and the subsequent read_exact
call would not start at the right place.
So you have to lock
it only once, and you have to call read_exact
on the same buffered reader that gave you the Lines
, to be sure no bytes are lost. At first glance this looks impossible: lines()
takes self
by value, so once you've called it, you can't call read_exact
on the same object. But there's a bit of a trick you can use.
The documentation for BufRead
contains this blanket impl
:
impl<'a, B: BufRead + ?Sized> BufRead for &'a mut B
&mut
references to things that implement BufRead
also implement BufRead
. So you can take a temporary &mut
reference of your StdinLock
, call lines()
on that, discard the Lines
in time to read_exact
the payload into buf
, and then start over again with another &mut
reference and another Lines
.
This approach necessitates adding another loop, with a flag has_header
to indicate whether to break the outer loop. It's not very pretty, but maybe you can work with it.
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;
}
}
A final note: It's unclear what should happen when the Content-Length
header is absent. If your original code worked, it would reuse the previously defined value (whatever the last content length was, or 0 for the first record). My version just uses 0 all the time. It's idiomatic in Rust to use Option
to represent possibly-uninitialized values such as content_length
, so initializing it to None
could be a better option if the absence of a Content-Length
header is an error that needs to be detected.
Upvotes: 2