15

If you check out the docs for Read, most of the methods accept a &mut self. This makes sense, as reading from something usually updates an internal offset so the next read returns different data. However, this compiles:

use std::io::Read;
use std::fs::File;

fn main() {
    let file = File::open("/etc/hosts").unwrap();
    let vec = &mut Vec::new();
    (&file).read_to_end(vec).unwrap();
    println!("{:?}", vec);
}

The file isn't mutable, but the data is certainly being read in. This seems incorrect to me. It was pointed out that there is an impl<'a> Read for &'a File, but the fact that an immutable instance is seemingly being mutated still seems odd.

Community
  • 1
  • 1
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • 1
    Does the answer to [Why can I call File.take() on a reference?](http://stackoverflow.com/q/31503429/395760) resolve this or are you asking *why* `Read` is implemented on `&File`? –  Jul 19 '15 at 16:46
  • 2
    @delnan The question here is: why is `&file` enough? One should expect `&mut file` be required. – mdup Jul 19 '15 at 16:53
  • 1
    @delnan (I didn't ask the question, but) I don't think that answers this question. There must be something else going on. `read_to_end` should work on either `&mut File` or `&mut &File`, but `(&file)` is neither of those. – fjh Jul 19 '15 at 16:54
  • 2
    @fjh: I believe `a.read_to_end(vec)` will call `Read::read_to_end(&mut a, vec)`, i.e. the method already sees `&mut &file`. – kennytm Jul 19 '15 at 16:56
  • @delnan the two questions sprang from the same source, but to have something useful to add, I'm curious both "why" and "how" `Read` can be implemented on an immutable reference and I've tweaked the question accordingly. – Shepmaster Jul 19 '15 at 17:00
  • there is no such thing as immutable in Rust. &mut just means, that you have exclusive access to a reference. – xophos Jul 20 '15 at 06:58

1 Answers1

14

As @kennytm pointed out, a.read_to_end(vec) is equivalent to Read::read_to_end(&mut a, vec), so (&file).read_to_end(vec) expands to Read::read_to_end(&mut &file, vec). In the latter expression, &file is a new temporary value of type &File. There is no problem with taking mutable references to an expression (e.g. &mut 42). This is exactly what happens here. The fact that the expression is a reference to an immutable value doesn't matter because we cannot actually mutate the value through a &mut &T.

Regarding the question why we don't need the File to be mutable: File is basically just a newtyped file descriptor, i.e. an index into an open-file table that is managed by the OS. read and friends will not change this descriptor at all, which is why the File does not need to be mutated. There is of course mutation going on, but that is done by the operating system on its own data structures and not in your user-land rust code.

fjh
  • 12,121
  • 4
  • 46
  • 46
  • 5
    +1 for "mutation [...] is done by the operating system on its own data structures and not in your user-land rust code." I believed having an (immut) `&File` meant that nothing could happen to your file; this answer show it's not the case, which is IMO counter-intuitive. Maybe this deserves a clarification in the docs. – mdup Jul 19 '15 at 18:02
  • 1
    @mdup perhaps it's just best for us to think of `File` as having a [cell](http://doc.rust-lang.org/std/cell/index.html) of some kind. – Shepmaster Jul 20 '15 at 00:00
  • 2
    @Shepmaster: Yeah, it's like a file has its own built-in mutex. The OS takes care of its synchronization. – sellibitze Jul 22 '15 at 13:42