2

Consider the following two code snippet:

fn main() {
    let mut foo = 1;
    let mut borrower = &mut foo;
    
    let mut borrower2 = &mut foo;  // error: cannot borrow `foo` as mutable more than once at a time

    *borrower2 = 2;
    *borrower = 3;

    println!("{}", foo);
}

This does not compile, as expected, because you can't have two mutable references to the same variable. However, if I replace the offending line as in the following, it works:

fn main() {
    let mut foo = 1;
    let mut borrower = &mut foo;
    
    let rr = &mut borrower;
    let borrower2 = &mut (**rr);
    
    *borrower2 = 2; // I can use both of these to mutate foo 
    *borrower = 3; // Shouldn't these borrows clearly overlap? 

    println!("{}", foo);
}

When reversing the two assignments, the following error is given:

error[E0506]: cannot assign to `*borrower` because it is borrowed
  --> src/main.rs:48:5
   |
45 |     let rr = &mut borrower;
   |              ------------- `*borrower` is borrowed here
...
48 |     *borrower = 3;
   |     ^^^^^^^^^^^^^ `*borrower` is assigned to here but it was already borrowed
49 |     *borrower2 = 2;
   |     -------------- borrow later used here
John Kugelman
  • 349,597
  • 67
  • 533
  • 578
JMC
  • 1,723
  • 1
  • 11
  • 20

3 Answers3

6

Yes, indeed, if Rust didn't have any other mechanisms that the one you probably already know, this would be an error. However, as might have guessed, there is an additional rule, in case of reborrowing. It's actually genuinely hard to find proper documentation on this feature, the best a quick and dirty google search led me to is this. I think this is because stacked borrows is a young idea, the related tooling is still WIP (like miri for instance), and so documentation is quite fresh too.

Reborrowing is closely related to stacked borrows. The idea is that, in the second example, &mut **r is not just a brand new borrow to foo, it's a borrow that was made through r. For this reason, Rust will consider that you invalidated r for the time &mut **r is valid, and once &mut **r ends, r begins valid again.

A nice way to think about this is to see each allocation (to keep it simple) as a stack. Each time you take a mutable borrow to the data contained in the allocation, you add that borrow to the stack. Of course, you can't take two mutable borrows at once, so the stack must be empty when you create a new mutable borrow. But then, if you have an active mutable borrow, you can create a new mutable borrow from that exclusive borrow by pushing it on top of the stack. Note that you can always only use the active mutable borrow, which is the one on top of the stack. When the lifetime of a borrow ends, it is popped from the stack. The important thing is that you can't pop something that is not on top of the stack, which translates to: a reborrow must live at most as long as its parent borrow. Breaking this rule is UB, and is (one of) the things that miri checks.

In fact, things can become a little bit more funky when you allow splitting an allocation or a mutable borrow into non-overlapping mutable borrows (like with split_at_mut). In this case, borrows don't grow like a stack, but like a tree, where you can only "pop" leaves, and can only add leaves to existing leaves...

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
jthulhu
  • 7,223
  • 2
  • 16
  • 33
  • Does the reborrow only happen at `&mut **rr` line or does every reference-to-reference constitute such a reborrow or a stacked borrow? I'm wondering because the compiler error refers to the line in which rr is declared, not to the line in which the second reference to foo is created. – JMC Jul 17 '23 at 22:03
  • 2
    Reborrowing preceeded Stacked Borrows, and is not related to it. Both are built upon the idea of stacking borrows, though. – Chayim Friedman Jul 18 '23 at 02:15
  • @ChayimFriedman so, IIUC, stacked borrows is the current memory model for reborrowing? Like, reborrowing is the language feature, stacked borrows the underlying specification? – jthulhu Jul 18 '23 at 06:08
  • @jthulhu No. Stacked Borrows is a model for unsafe code. It allows more than reborrows, and it includes raw pointers. Reborrows are a mechanism ensured at compile-time, statically. – Chayim Friedman Jul 18 '23 at 06:16
  • @ChayimFriedman yeah but, AFAIK, exclusive access to some data has to respect the stacked borrow model, which can be verified at compile time for borrows (with reborrows), and which you promise to respect in unsafe code. Or I've missed something? – jthulhu Jul 18 '23 at 06:20
  • 2
    Stacked Borrows did not exist when reborrows were first created. Back then there was only what the compiler allows, and vague constraints you need to hold in unsafe code. Stacked Borrows gave to (some of) those constraints clarity and preciseness. It also describes the memory model and what optimizations the compiler can do. It does mean reborrowing adheres to its rules, otherwise the language would be unsound; but they were not designed around it. And Stacked Borrows is not even the official memory model of Rust. – Chayim Friedman Jul 18 '23 at 06:41
  • @JMC *reborrowing* is just the `&mut *` part. – jthulhu Jul 18 '23 at 06:58
  • Do note that Stacked Borrows is _not_ the official model of rustc (there's none); instead it's a _tentative_ model of what the rules could be, and indeed there's a competing model in the name of Tree Borrows now... – Matthieu M. Jul 18 '23 at 10:11
1

This is called reborrowing. Reborrowing happens when a mutable reference's type changes. From docs:

let mut foo: i32     = 22;
let r_a: &'a mut i32 = &'a mut foo;
let r_b: &'b mut i32 = &'b mut *r_a;

use(r_b);

In this case, the supporting prefixes of *r_a are *r_a and r_a (because r_a is a mutable reference, we recurse). Only one of those, *r_a, is a deref lvalue, and the reference r_a being dereferenced has the lifetime 'a. We would add the constraint that 'a: 'b, thus ensuring that foo is considered borrowed so long as r_b is in use. Without this constraint, the lifetime 'a would end after the second borrow, and hence foo would be considered unborrowed, even though *r_b could still be used to access foo.

Example 2. Consider now a case with a double indirection:

let mut foo: i32     = 22;
let mut r_a: &'a i32 = &'a foo;
let r_b: &'b &'a i32 = &'b r_a;
let r_c: &'c i32     = &'c **r_b;
// What is considered borrowed here?
use(r_c);

Just as before, it is important that, so long as r_c is in use, foo is considered borrowed. However, what about the variable r_a: should it considered borrowed? The answer is no: once r_c is initialized, the value of r_a is no longer important, and it would be fine to (for example) overwrite r_a with a new value, even as foo is still considered borrowed. This result falls out from our reborrowing rules: the supporting paths of **r_b is just **r_b. We do not add any more paths because this path is already a dereference of *r_b, and *r_b has (shared reference) type &'a i32. Therefore, we would add one reborrow constraint: that 'a: 'c. This constraint ensures that as long as r_c is in use, the borrow of foo remains in force, but the borrow of r_a (which has the lifetime 'b) can expire.

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
Yilmaz
  • 35,338
  • 10
  • 157
  • 202
1

Disclaimer: not quite satisfied with the answers so far. I think a more in-depth answer would be more valuable.

Ownership & Borrowing

The two cornerstones concept of the handling of values (and references) in Rust are ownership and borrowing:

  • Ownership can be transferred: the value is moved from one variable to another.
  • Ownership can be "lent out" temporarily: a reference (mutable or not) to the value is created, borrowing it.

It is also possible to clone (or copy) a value, in which case a new value is created, independent from the original. In this case, no transfer of ownership occurs, and the borrow of the value that is cloned (or copies) ends by the time the new value is available.

Copying References

Immutable (shared) references are copyable (they implement the Copy trait), since you can have any number of immutable references to a given value at any moment in time.

Mutable (unique) references are NOT, however, as otherwise, they'd wouldn't be unique.

This means that assigning a reference to a variable will have different properties, depending on whether it's immutable or not:

let x = 3;
let y = &x;
let z = y; //  z is a Copy of y

let mut a = 7;
let b = &mut a;
let c = b; //  b is _moved_ to c, the variable b cannot be used afterwards.

The latter is a key motivation for being able to temporarily borrow a reference (or mutable reference) of a from b, for example, to make a function call:

fn foo(r: &mut i32) {
    *r += 1;
}

fn main() {
    let mut a = 7;
    let b = &mut a;

    foo(b);

    println!("{b}"); // Expected error, `b` was moved out.
}

Enter Re-borrowing

The solution to the above issue is the idea of re-borrowing, which is borrowing the referred value out of its reference, temporarily.

The key idea is that it works very similarly to borrowing from a value. When you borrow from a value:

  • The variable you borrow from is intact.
  • The variable is inaccessible -- in case of mutable borrowing -- for the lifetime of the borrow (a subset of the lifetime of the reference).

And thus re-borrowing allows the same key idea:

  • The reference you borrow from is intact.
  • The reference you borrow from is inaccessible -- in cast of mutable borrowing -- for the lifetime of the borrow.

The syntax of re-borrowing is a bit special. You can't write &reference as that would create a reference to the reference, which is valid, but not quite what you want. Thus you are left writing &*reference to indicate that you want a reference to what reference refers to itself.

Due to this syntax being quite a bit verbose, and not too ergonomics, re-borrowing is usually done automatically for you. Taking our previous example:

fn foo(r: &mut i32) {
    *r += 1;    //  Re-borrow 1
}

fn main() {
    let mut a = 7;
    let b = &mut a;

    foo(b);     //  Re-borrow 2

    println!("{b}");
}

Starting from the bottom, when a function taking a mutable reference is called with a mutable reference, the compiler automatically inserts &mut *, so that in fact the example does not lead to a compiler error even though b cannot be copied, only moved.

Similarly, the *r += 1 is actually re-borrowing. This time the * is inserted manually, and the compiler then applies the &mut implicitly as it rewrites the expression to (*r).add_assign(1) where add_assign takes &mut self.

Re-borrowing is everywhere, it's just so automatic you probably never noticed.

Applying re-borrowing

Let's examine your examples to figure out what's going on:

fn main() {
    let mut foo = 1;
    let mut borrower = &mut foo;
    
    let rr = &mut borrower;          //  (1)
    let borrower2 = &mut (**rr);     //  (2)
    
    *borrower2 = 2;                  //  (3)
    *borrower = 3;                   //  (4)

    println!("{}", foo);
}

In order:

  1. Creates a mutable reference to a mutable reference to foo:
    • rr is of type &mut &mut i32.
    • borrower is now borrowed.
  2. Create a mutable reference to foo:
    • borrower2 is of type &mut i32.
    • rr is now reborrowed.
  3. Assign to the value referred to by borrower2.
  4. Assign to the value referred to by borrower.

Why is (4) allowed?

The short answer is that the lifetime of the borrow of rr by borrower2 ended, and the lifetime of the borrow of borrower by rr ended, and thus borrower is no longer borrowed.

The long answer is that originally the Rust compiler would consider a variable borrowed for the lifetime of its reference, which was typically until the end of the scope in which the reference was created. This was inflexible, and required introducing extra scopes:

fn main() {
    let mut foo = 1;
    let mut borrower = &mut foo;
    
    {
        let rr = &mut borrower;          //  (1)
        let borrower2 = &mut (**rr);     //  (2)
    
        *borrower2 = 2;                  //  (3)
    }

    *borrower = 3;                   //  (4)

    println!("{}", foo);
}

To make our life easier, Non-Lexical Lifetimes (NLL) were implemented, giving the latitude to the compiler to end borrows earlier than the end of the scope of the references that created the borrow. In general, this means that the borrow only lasts until the last use of the reference.

And therefore your example works without the (manual) extra scope.

What if you reverse the assignments? That is:

fn main() {
    let mut foo = 1;
    let mut borrower = &mut foo;
    
    let rr = &mut borrower;          //  (1)
    let borrower2 = &mut (**rr);     //  (2)
    
    *borrower = 3;                   //  (A)
    *borrower2 = 2;                  //  (B)

    println!("{}", foo);
}

Well, then NLL doesn't save you any longer, since it only contracts the tail of the borrow, not its head. It could theoretically only borrow from first to last use, rather than from creation to last use, but it doesn't, and therefore:

  • borrower2 is last used at (B).
  • Thus, rr must be borrowed from (2) to (B).
  • Thus, borrower must be borrowed from (1) to (B).
  • Thus, borrower is borrowed at (A) -- and it's an error to try to re-borrow it again.

All clear?

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • Thanks for the in-depth explanation. I think I get it in general, but I think there might be some inexactness in your wording in the last paragraph because it seems to somewhat contradict the last paragraph of Yilmaz's response, in which the intermediate reference `rr`'s borrow of the first reference `borrower` is not required to live for that long, if I understand it correctly. Given the error message, shouldn't the very last bullet point be "it's an error to assign to it while it's borrowed"? – JMC Jul 18 '23 at 15:04
  • @JMC: Assignment requires re-borrowing mutably, so both statements are correct. I'm not _quite_ sure with regard to the borrow of `rr`... I'm not sure it can be observed directly. You can't drop `rr` while it's borrowed, but since the lifetime of the borrow is disjoint from the lifetime of the variable... it may be it's shorter? Hard to prove either way. – Matthieu M. Jul 18 '23 at 15:31