9.7 Reborrowing: The Automatic Coercion from &mut to &
Alright, let’s talk about one of Rust’s more subtle party tricks: reborrowing. You’ve probably already experienced this without even realizing it, which is a testament to how well the compiler engineers designed this feature. It’s the reason you don’t have to tear your hair out nearly as often as you might expect when juggling mutable references.
Imagine you have a &mut T—your coveted exclusive, mutable reference. You want to call a function that takes a &T, an immutable reference. In a strictly literal world, this shouldn’t work. You have an exclusive mutable ticket, and the function just wants a shared, read-only ticket. They’re different types! But try it:
fn inspect(data: &i32) {
println!("The value is: {}", data);
}
let mut x = 42;
let mutable_ref = &mut x;
// This compiles and works perfectly.
inspect(mutable_ref);
// And we can still use our mutable_ref exclusively afterward!
*mutable_ref += 1;
println!("Now it's: {}", mutable_ref);
Wait, what? How did we pass a &mut i32 to a function that expects a &i32? This feels like we’re violating the rules. We’re not. This is the magic of automatic reborrowing. The compiler, seeing the type mismatch, implicitly creates a new, temporary immutable reference (&*mutable_ref) for the duration of the function call. It’s as if you wrote inspect(&*mutable_ref) yourself.
Why This Isn’t Cheating
This works because it’s semantically sound. The key insight is the temporary nature of the new reference. The inspect function only gets access for its own lifetime, which is strictly shorter than the lifetime of the original mutable_ref. While inspect is running, the world is frozen through that new immutable lens. The original mutable_ref is “lent out” and cannot be used until the function call—and thus the temporary borrow—ends. The moment inspect returns, the temporary &i32 vanishes, and your exclusive control via mutable_ref is fully restored. The borrow checker ensures all of this happens correctly.
The Real Magic: &mut to &mut Reborrowing
The more impressive, and more common, sleight of hand happens when you pass a &mut T to a function that also expects a &mut T.
fn increment(data: &mut i32) {
*data += 1;
}
let mut x = 42;
let ref1 = &mut x;
// This compiles. But wait, isn't `ref1` already the one and only mutable reference?
increment(ref1);
// And we can still use ref1 afterward!
println!("{}", ref1);
This is the same trick, just with mutable reborrowing. The compiler creates a temporary mutable reference (&mut *ref1) for the call to increment. The lifetime of this new reference is strictly confined to the function call. This is why we can have what looks like two mutable references to x:
- The original
ref1, which is “borrowed” and inactive during the call. - The new, temporary
&mutinsideincrement.
As soon as increment returns, the temporary reference’s lifetime ends, and ref1 regains its exclusive status. This automatic coercion is what makes pervasive use of &mut references ergonomic. Without it, you’d be writing increment(&mut *ref1) all over the place, which is just noisy and awful.
Where the Illusion Breaks (A Bit)
Reborrowing is smart, but it’s not clairvoyant. It follows lexical scoping rules. The temporary reference’s lifetime lasts until the end of the statement or block where it’s used. This usually does what you want, but you can trip it up.
The classic pitfall is trying to use the original reference while a reborrow is still theoretically alive. This most often happens with methods that take &mut self.
let mut v = vec![1, 2, 3];
let mutable_ref = &mut v;
// This is a reborrow: .push() takes &mut self
mutable_ref.push(4); // Temporary mutable borrow occurs here
// Let's say you try to use the original reference *on the next line*...
// println!("First element: {}", mutable_ref[0]); // This would be fine!
// But try to use it in the same statement? Trouble.
let first_item = mutable_ref.get(0); // Compiler error!
// ^^^^^^^^^^^^ immutable borrow occurs here
println!("First element after push: {}", first_item.unwrap());
Wait, why? The .push(4) call finishes, so the temporary reborrow should be over, right? The issue is that for method chains or complex expressions, the compiler can sometimes extend the lifetime of a temporary reborrow longer than you’d intuitively expect—often to the end of the entire statement. In the code above, the compiler might see the entire let first_item = ... line as one big expression and decide the reborrow from mutable_ref.get(0) (which is an immutable borrow) is still active when we try to use mutable_ref again for the println!. The fix is to break the statement up, making the lifetimes obvious to both you and the compiler.
The Golden Rule
Remember this: A reborrow is always a temporary, shorter-lived reference derived from a longer-lived parent reference. The parent is “inaccessible” for the duration of the reborrow. This is the core rule that allows the borrow checker to remain ruthlessly safe while letting you write code that doesn’t look like it was generated by a code-golfing robot. It’s not a loophole; it’s a carefully designed pressure release valve for the ownership system. Use it with confidence.