Move semantics with references

I am trying to wrap my head around behaviour with the "move semantics" when dealing with references and looking for confirmation or correction.

So when I try to run the following code

fn main() {
    let a : &mut Box<usize> = &mut Box::new(0);
    
    let b : &mut Box<usize> = a;
    
    let c : &Box<usize> = a;
    
    let d : &Box<usize> = &a;
}

I get the following two errors

error[E0502]: cannot borrow `*a` as immutable because it is also borrowed as mutable
 --> src/main.rs:6:27
  |
4 |     let b : &mut Box<usize> = a;
  |                               - mutable borrow occurs here
5 |     
6 |     let c : &Box<usize> = a;
  |                           ^ immutable borrow occurs here
...
9 | }
  | - mutable borrow ends here

error[E0502]: cannot borrow `a` as immutable because `*a` is also borrowed as mutable
 --> src/main.rs:8:28
  |
4 |     let b : &mut Box<usize> = a;
  |                               - mutable borrow occurs here
...
8 |     let d : &Box<usize> = &a;
  |                            ^ immutable borrow occurs here
9 | }
  | - mutable borrow ends here

My understanding is that when I try to "move" a reference out of a binding(in this case, a), instead of "moving" the references between bindings, it becomes borrowing

  • *a (in the case of let b ... and let c ...)
  • or a (in the case of let d ...)

And the normal reference rules apply regarding *a and a afterwards, i.e.

  • cannot borrow *a or a mutably if *a or a was already borrowed mutably or immutably
  • cannot borrow *a or a immutably if *a or a was already borrowed mutably
  • can borrow immutably both *a and a if *a or a was borrowed immutably previously

Is my understanding correct?

Thanks for your time!

Where the compiler can choose between moving a borrowed reference and reborrowing, it will choose reborrowing. That's what's happening here: b reborrows the reference from a, and attempting to touch a itself or create any other references to the referrent of a while b is in scope will cause that error. Putting b in a more limited scope allows the rest of that program to compile:

fn main() {
    let a : &mut Box<usize> = &mut Box::new(0);
    
    {
        let b : &mut Box<usize> = a;
    } // b goes out of scope and releases its borrow here

    let c : &Box<usize> = a;
    
    let d : &Box<usize> = &a;
}

This thread goes into more detail about reborrowing, and cites some useful sources.

1 Like

The original code will work as written once non-lexical lifetimes is stable.

#![feature(nll)]

fn main() {
    let a: &mut Box<usize> = &mut Box::new(0);
    
    let b: &mut Box<usize> = a;
    
    let c: &Box<usize> = a;
    
    let d: &Box<usize> = &a;
}
2 Likes