18

Here's a line from the 2nd edition Rust tutorial:

let mut f = File::open(filename).expect("file not found");

I'm of the assumption that the file descriptor is a wrapper around a number that basically doesn't change and is read-only.

The compiler complains that the file cannot be borrowed mutably, and I'm assuming it's because the method read_to_string takes the instance as the self argument as mutable, but the question is "why"? What is ever going to change about the file descriptor? Is it keeping track of the cursor location or something?

error[E0596]: cannot borrow immutable local variable `fdesc` as mutable
  --> main.rs:13:5
   |
11 |     let fdesc = File::open(fname).expect("file not found");
   |         ----- consider changing this to `mut fdesc`
12 |     let mut fdata = String::new();
13 |     fdesc.read_to_string(&mut fdata)
   |     ^^^^^ cannot borrow mutably

The whole source:

fn main() {
    let args: Vec<String> = env::args().collect();
    let query = &args[1];
    let fname = &args[2];
    println!("Searching for '{}' in file '{}'...", query, fname);

    let fdesc = File::open(fname).expect("file not found"); //not mut
    let mut fdata = String::new();
    fdesc.read_to_string(&mut fdata)
        .expect("something went wrong reading the file");

    println!("Found: \n{}", fdata);
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
James M. Lay
  • 2,270
  • 25
  • 33
  • 3
    Not a Rust expert myself, but I think this is because the File is conceptually mutable, not the number representing it. The in-kernel file represented by the file handle number *is* mutable. Reading or writing it changes its contents and moves the kernel's current position in the file. – Zan Lynx Dec 05 '17 at 19:14
  • @ZanLynx: Your intuition is correct, and it goes beyond. When using the decorator pattern, as `BufReader` does, the Rust object itself is mutated along the way (the buffer inside). – Matthieu M. Dec 06 '17 at 07:50

1 Answers1

14

I'm assuming it's because the method read_to_string takes the instance as the self argument as mutable

Yes, that's correct:

fn read_to_string(&mut self, buf: &mut String) -> Result<usize>

The trait method Read::read_to_string takes the receiver as a mutable reference because in general, that's what is needed to implement "reading" from something. You are going to change a buffer or an offset or something.

Yes, an actual File may simply contain an underlying file descriptor (e.g. on Linux or macOS) or a handle (e.g. Windows). In these cases, the operating system deals with synchronizing the access across threads. That's not even guaranteed though — it depends on the platform. Something like Redox might actually have a mutable reference in its implementation of File.

If the Read trait didn't accept a &mut self, then types like BufReader would have to use things like internal mutability, reducing the usefulness of Rust's references.

See also:

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • I'm confused why in one instance you _can_ `read_to_end` with an immutable `File` (the linked "See also") but you _cannot_ `read_to_string` with an immutable `File`? What am I missing? Why can't `read_to_string` just implement `read_to_end` under the hood? – Austin Heller Oct 07 '22 at 17:42
  • 1
    @AustinHeller I don't follow — you [_can_ call `read_to_string` on an immutable reference to a `File`](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=b7993c74c493b72fce1d73d043fad973). Note that an immutable **reference** to a `File` is not the same type as an immutable `File`. – Shepmaster Oct 10 '22 at 14:58
  • Thanks for the quick reply. I didn't notice that the linked "See also" was using an immutable File reference. I'm still confused though, in one scenario we can use a mutable File instance and in the other we're permitted to use an immutable File reference to an immutable File? I'm very new to Rust, btw, hence what may seem like basic questions. Main question: Why would Rust be okay with an immutable reference to an immutable File but *not* okay with an immutable File? – Austin Heller Oct 11 '22 at 16:43
  • 1
    @AustinHeller in the working case, you create a temporary immutable reference to a `File` then implicitly take a mutable reference to the temporary, producing an `&mut &File`. – Shepmaster Oct 13 '22 at 01:39