Hello everyone ,
while writing a bit of Rust code I noticed that I could replace a move of a value in a certain function with an immutable reference to it and still call a method on it inside the function which takes &mut self. Here's a somewhat minimal example:
use std::fs::File;
use std::io::Read;
fn read_fn(mut input: impl Read) {
let mut buf = Vec::new();
input.read_to_end(&mut buf).unwrap();
}
fn main() {
let file = File::open("test").unwrap();
read_fn(&file);
file.metadata().unwrap();
}
In the real code, I just added the & to the argument in read_fn() and did not expect that it would compile just like that, but it did! As can be seen here.
What I actually expected was the following: due to the fact that the variable file has not been declared mutable you cannot take a mutable reference out of it (and I clearly do not do this here), so the code should fail at the call to read_to_end(). Furthermore I thought that the mut parameter in read_fn only turns the variable input into a mutable variable of immutable references to File.
The fact that I can still call a method on file after read_fn() means that no weird move happened here either so what is going on here exactly? I just cannot wrap my head around it and searching the internet did not yield any results for me either. I'm not even sure what I need to search for exactly.
Also note that impl Read can just be replaced with &File in this particular case and it will still be fine, so the generic parameter isn't doing anything unexpected for me but I just left it in to indicate that I could just add the & and the code would still compile.
&File implements Read, which is why this works. input.read_to_end is taking a &mut &File (which is why the param needs to be annotated as mut.) Reading a File doesn't actually mutate any state in the File - at least not as Rust is concerned.
This is because, &T means shared reference, not immutable reference. There are plenty of ways to mutate through a shared references. See and Cell and Mutex from std
Thanks for the responses guys, I really missed the part with &File implementing Read, which is why this code didn't already fail at the function signature . So I checked the actual source code right now and yes, the inner variable wrapped by the File struct has platform-specific implementations of a "read" function which takes &self as parameter. @RustyYato Thanks for the pointer, I understand what you're saying but my brain thought for a moment that the code was doing essentially the equivalent of:
let file = File::open("test")?;
file.read_to_end(...)?;
which definitely does not compile, since read_to_end takes &mut self. But everything works due to the aforementioned impl Read for &File:
let file = File::open("test")?;
let mut f = &file;
f.read_to_end(...)?;
Still looks pretty weird to me but oh well. So thanks again for the responses.