Search code examples
rust

Why isn't it allowed to change mutable reference if one or multiple immutable references are within the scope?


New to Rust. If I understand it correctly, a mutable reference can not be allowed to change the variable if one or multiple immutable references are within the scope (The Rust Book section 4.2).

fn main () {
  let mut s = String::from("hello");
  let r1 = &mut s; 

  change(r1); 

  let im1 = &s; 

  println!("{im1}"); 

  // change(r1); // error. 
  change(&mut s); // okay. 
}

Why does change(r1) lead to error but change(&mut s) is okay.

The immuatable reference im1 is no longer used so it should be fine to modify the string via r1.

fn main () {
  let mut s = String::from("hello");
  let r1 = &mut s; 

  change(r1); 

  {
    let im1 = &s; 
    println!("{im1}"); 
  }

  change(r1); // error. 
  // change(&mut s); // okay. 
}

This also results in a similar error. Rust version rustc 1.81.0.


Solution

  • The rule is not that you cannot modify data using mutable (aka exclusive) references, while you hold other immutable (aka shared) references. It is actually stronger. There cannot ever exist exclusive reference, while some shared references to the data also exist.

    So in your second example it doesn't matter, that shared reference is created in the inner scope, and is dropped before you call change with &mut. All that matters is that since exclusive reference is used after your inner block, then it must be "alive" during the scope of the inner block. It doesn't matter if you actually use it, or not. So you try to have both mutable and immutable references at the same time, which is not allowed.

    So why your examples work if you do not reuse existing exclusive reference r1, and create a new one? Normally rust drops variables in the reverse order of their creation. However this would be really painful if it was applied strictly to the references, because you would have to create manual scopes, or call drop yourself every time, you would want to create short-lived references. So the compiler is a little bit smarter here. It analyzes how long reference must be alive, and as soon as it ends it drops it.

    Here's an example of what compiler does:

    fn main () {
      let mut s = String::from("hello");
      let r1 = &mut s; // <- creates mutable reference
    
      change(r1); // <- uses mutable reference
                  // <- here compiler sees, that r1 is not longer used,
                  //    so it can be dropped now (which would not be
                  //    the case if you would uncomment `change(r1)`).
    
      let im1 = &s; // <- creates shared reference (there are no other references,
                    //    at this point).
    
      println!("{im1}"); // <- uses shared reference
                         // <- as with r1 rust sees that im1 is no longer used,
                         //    so it can be dropped here.
    
      // change(r1); // error. <- if you would uncomment this, then r1 could not
                     //           have been dropped earlier
      change(&mut s); // okay. <- creates _new_ mutable reference
                      //          (previous immutable one is already dropped).
    }