Why is this the case?
Because it's valid for an implementer of Read
to read the passed-in buffer first. If you passed in uninitialized data and the implementer of Read
looked at the buffer, then there would be undefined behavior in purely safe code. Disallowing that, statically, is a large selling point of Rust.
use std::io::{self, Read};
struct Dummy;
impl Read for Dummy {
fn read(&mut self, buffer: &mut [u8]) -> io::Result<usize> {
let v: u8 = buffer.iter().sum(); // Reading from the buffer
buffer[0] = v;
Ok(1)
}
}
fn main() {
let mut data = [0, 1, 2];
Dummy.read(&mut data).unwrap();
println!("{:?}", data);
}
Why does Read::read
not prevent reading from the buffer?
There isn't a language construct that can be used to impose that restriction. Unlike some other languages, Rust doesn't have "out parameters". Even if it did, I could see an implementer of Read
wanting the ability to read the data that it just wrote. For example, a reader that counted the number of newlines that passed though it.
Why does Read::read
not accept MaybeUninit
?
MaybeUninit
didn't exist in Rust 1.0 — it was only stabilized in Rust 1.36. We wanted the ability to read from files in Rust 1.0. Due to Rust's backwards-compatiblity guarantees, the method's signature cannot be changed now.
Why is Read::read
not unsafe
?
This would have been the main (only?) technique to support uninitialized data, but it would have come at a high cost. unsafe
isn't a tool that experienced Rust programmers choose trivially. When we do use it, we generally strive really hard to minimize its scope.
If Read::read
were unsafe, then every implementer would have to think about how to properly meet the unsafe criteria. This is a high burden to place on "simple" adapters.
Is there any workaround? Are there any solutions being worked on by the Rust team?
The unstable Read::initializer
method is one proposed solution, but it's likely not the preferred route.
RFC 2930 provides an updated attempt, and discusses much of the backstory and challenges.
See also:
For your specific case, you can probably use Read::take
and Read::read_to_end
to read all of the bytes you want into an empty (uninitialized!) Vec
and then convert a Vec<T>
to a Vec<U>
without copying the vector. You will need to somehow ensure that you have properly aligned the Vec
for f32
as it starts out as only aligned for u8
.