Why is `std::fs::File` different?

I am leaning rust and using it to write something.
Today, I found that the behavior of the std::fs::File is different from the rust book. Please tell me why it is different.

use std::io::Read;

fn main() {
    let mut buf = Vec::new();
    let str = "ABC";

    
    let file = std::fs::File::open("path/to/file").unwrap();
    let mut mut_file_ref = &file;
    mut_file_ref.read_to_end(&mut buf).unwrap();


    let string = String::from("");
    let mut mut_string_ref = &string;
    mut_string_ref.push_str(str);

    
    println!("{} {:?}", file.metadata().unwrap().len(), buf.len());
}

  --> src/main.rs:15:5
   |
15 |     mut_string_ref.push_str(str);
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `mut_string_ref` is a `&` reference, so the data it refers to cannot be borrowed as mutable
   |
help: consider changing this to be a mutable reference
   |
14 |     let mut mut_string_ref = &mut string;
   |                               +++


&File implements Read, so you can call read_to_end using a &mut &File. You only need the &mut because the trait demands it; neither the &File nor the File are actually modified.

How can that work? Under the hood, a File is just some identifier the operating system has given you which is passed back when you make system calls. Whatever mutation is necessary doesn't happen to the File itself, it happens to some data beyond the OS API boundary.

You may not have learned about Rust's interior mutability (aka shared mutability) features yet, but once you do, you can think of File as having interior mutability (albeit supplied by the OS and not by native Rust code).

A String doesn't have any interior mutability.

7 Likes

How so?

Your reply has been very helpful to me, thanks!