:#/ GSLF Journal
← :#/ GSLF Journal

Rustlers Atom 2.7: Reborrowing

<figure class="video" style="margin:0;">
<div style="position:relative;width:100%;padding-bottom:56.25%;height:0;overflow:hidden;">
<iframe src="https://www.youtube.com/embed/XbFiW89P64c?si=OjMmRhFu7aTSG0Vh"
title="YouTube video player"
style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen></iframe>
</div>
</figure>

If Rust strictly followed the "Move" rule for mutable references, programming would be incredibly tedious. Technically, a &mut T is not Copy. This means if you pass a mutable reference to a function, it should be moved into that function, and the original variable holding that reference should become unusable.

But . . . this code works:

fn modify(x: &mut i32) { *x += 1; }

fn main() {
    let mut data = 10;
    let r = &mut data;
    
    modify(r); // r is moved into modify?
    modify(r); // ❌ This should fail if r was moved!
}

When you use a mutable reference in a place that expects a mutable reference, Rust implicitly inserts a reborrow instead of a move. It creates a temporary reference &mut *r that borrows from the original reference r.

This temporary reference has a shorter lifetime. While the reborrow is active (i.e., inside the modify function), the original r is temporarily "frozen" or suspended. Once modify returns, the reborrow dies, and r wakes up, allowing you to use it again.

This allows you to thread a single mutable reference through multiple function calls sequentially without losing ownership of the reference itself.

Two-Phase Borrows#

There is a specific edge case that used to plague Rust users: using a value while modifying it.

let mut v = vec![1, 2, 3];
v.push(v.len());

Under strict borrowing rules, this looks illegal:

v.push(...) takes &mut v (mutable borrow).
v.len() takes &v (immutable borrow).

The arguments are evaluated before the function call. So strictly speaking, v.len() (immutable borrow) happens while we are preparing to call push (which needs a mutable borrow). However, the compiler is now smart enough to see that the modification inside push doesn't actually happen until after we have calculated the length. This is Two-Phase Borrowing.

Reservation Phase: The compiler creates a "reservation" for the mutable borrow. It acknowledges we will need to mutate v, but we haven't started writing yet.
Activation Phase: Once all arguments (like v.len()) are evaluated, the mutable borrow is fully activated, locking out all other access.