What is reborrowing in Rust

Reborrowing is an automatic Rust mechanism that creates a temporary reference from an existing one to satisfy function signatures without consuming the original reference.

The reference that never leaves your hand

You hold a mutable reference to a configuration object. You pass it to a function that tweaks one setting. You immediately pass it to another function that validates the result. You expect Rust to consume your reference on the first call, leaving you with nothing for the second. The compiler does not consume it. It hands out a temporary copy of the reference, lets the function do its work, and returns the original handle to you.

That mechanism is reborrowing. It is the reason you can pass the same &mut T to multiple functions in sequence without manually cloning pointers or restructuring your control flow. It is also the reason method chains work on mutable references. The compiler quietly shortens the lifetime of the reference for the duration of the call, then restores your original handle.

How reborrowing actually works

Rust's borrow checker tracks two things for every reference: what it points to, and how long it is allowed to live. When you create let ref1 = &mut val, you get a reference that lives until the end of its scope. Passing ref1 to a function that expects &mut T would normally move the reference, just like passing an owned String moves the string. Moving a reference would mean you lose the ability to use it afterward.

Reborrowing changes that rule. Instead of moving ref1, the compiler creates a new, shorter-lived reference that points to the same memory. This temporary reference lives only for the duration of the function call. The original ref1 stays exactly where it is, untouched and fully valid.

Think of it like a master key for a building. You hold the master key. When a contractor needs access to a room for an hour, you do not hand over the master key. You cut a temporary key that works only for that hour. The contractor finishes, the temporary key expires, and you still hold the master key. You can cut another temporary key for the next contractor without losing control.

The compiler applies this rule to both &mut T and &T. Mutable reborrowing is more visible because &mut enforces exclusive access. Immutable reborrowing happens constantly in the background whenever you pass a shared reference to a function.

Minimal example

/// Takes a mutable reference and increments the value.
fn takes_mut(x: &mut i32) {
    *x += 1;
}

fn main() {
    let mut val = 5;
    
    // Create a mutable reference that lives until the end of main
    let ref1 = &mut val;
    
    // Implicit reborrow: the compiler creates a temporary &mut i32
    // that lives only for this function call. ref1 is not moved.
    takes_mut(ref1); 
    
    // ref1 is still valid. Another implicit reborrow happens here.
    takes_mut(ref1); 
    
    // You can also reborrow explicitly when the compiler needs a nudge
    let ref2 = &mut *ref1;
    takes_mut(ref2);
    
    println!("{}", val); // Prints 8
}

The first two calls to takes_mut rely on implicit reborrowing. The compiler inserts the temporary reference automatically because function arguments are a coercion site. The third call shows explicit reborrowing with &mut *ref1. The dereference *ref1 yields the underlying i32, and &mut wraps it back into a new reference. This syntax is useful when you need to control the reborrowing yourself, such as in macros or when working with generic types where the compiler's implicit coercion does not trigger.

Convention aside: the Rust community prefers implicit reborrowing whenever possible. Explicit &mut *ref is reserved for cases where the type system needs a clear boundary. Writing &mut *ref everywhere clutters the code and signals that you are fighting the compiler instead of letting it handle lifetime shortening.

Trust the implicit coercion. It exists to keep your code readable.

What the compiler does behind the scenes

When the borrow checker encounters takes_mut(ref1), it does not transfer ownership of ref1. It calculates the exact span of code where the temporary reference is active. That span starts at the function call and ends at the semicolon. The checker verifies that no other active references conflict with this temporary span. Once the call returns, the temporary reference is dropped. The original ref1 regains full exclusive access.

This lifetime shortening is the core mechanic. Reborrowing never extends a lifetime. It only shrinks it. If you try to store the reborrowed reference somewhere that outlives the call, the compiler rejects it. The temporary reference cannot escape its scope.

The same rule applies to &T reborrowing, but it is less restrictive because multiple immutable references can coexist. When you pass &val to a function, the compiler creates a shorter &val for the call. The original &val remains valid. You rarely notice it because immutable borrowing allows overlap. Mutable reborrowing is what you notice, because &mut normally forbids overlap. Reborrowing creates a controlled, time-boxed overlap that the borrow checker can verify statically.

Realistic usage pattern

Consider a state machine that processes events. You hold a mutable reference to the current state. You pass it to a handler that updates the state based on an event. You then need to check the updated state to decide whether to continue or exit. Without reborrowing, you would have to restructure the code to pass the owned value, or use interior mutability, or split the reference across multiple scopes.

/// Represents a simple counter state
struct Counter {
    value: i32,
    active: bool,
}

/// Processes an event and mutates the counter
fn process_event(counter: &mut Counter) {
    counter.value += 1;
    if counter.value > 10 {
        counter.active = false;
    }
}

/// Checks if the counter should keep running
fn should_continue(counter: &Counter) -> bool {
    counter.active && counter.value < 20
}

fn main() {
    let mut counter = Counter { value: 0, active: true };
    
    // Hold a mutable reference for the entire loop
    let state_ref = &mut counter;
    
    while should_continue(&*state_ref) {
        // Implicit reborrow passes a temporary &mut to process_event
        // The original state_ref remains valid for the next iteration
        process_event(state_ref);
    }
    
    println!("Final value: {}", counter.value);
}

The loop holds state_ref across iterations. Each call to process_event triggers an implicit reborrow. The temporary &mut Counter lives only for the duration of process_event. The original state_ref survives the call, allowing the next iteration to borrow from it again. The should_continue call uses &*state_ref to create an immutable reborrow, which coexists safely with the mutable reborrow because the mutable one has already ended.

This pattern appears constantly in parsers, game loops, and UI state updates. You hold a reference to a large structure, pass slices of it to handlers, and continue using the same reference afterward. Reborrowing makes the control flow linear instead of nested.

Pitfalls and compiler errors

Reborrowing shortens lifetimes. It does not extend them. If you try to keep a reborrowed reference alive beyond the function call, the compiler stops you with E0597 (borrowed value does not live long enough). The temporary reference dies at the semicolon. You cannot store it in a struct, return it, or hand it to an async task.

Another common trap is assuming reborrowing bypasses exclusive access rules. It does not. If you hold a mutable reference and try to create an immutable reference to the same data while the mutable one is still active, you get E0502 (cannot borrow as immutable because it is also borrowed as mutable). Reborrowing only works when the temporary reference's lifetime is strictly contained within the original reference's scope.

Explicit reborrowing can also trigger E0507 (cannot move out of borrowed content) if you misuse dereferencing. Writing &mut *ref is safe because *ref yields a place, not a moved value. Writing &mut ref without the dereference attempts to take a reference to the reference itself, which changes the type to &mut &mut T. That is rarely what you want and often breaks generic bounds.

Convention aside: when you see a lifetime error involving references, check whether you are accidentally holding a reborrowed reference longer than the call site. Drop the temporary, let the compiler reborrows implicitly, and the error usually disappears.

Treat the borrow checker's lifetime span as a hard boundary. Do not try to stretch it.

When to use reborrowing versus alternatives

Use implicit reborrowing when you pass a reference to a function or method and need to keep using the original reference afterward. The compiler handles the lifetime shortening automatically at coercion sites.

Use explicit &mut *ref when you need to create a reference with a specific, shorter lifetime that the compiler cannot infer, such as inside macros, when working with raw pointers, or when implementing custom iterators that yield references.

Reach for &T when you only need to read the data. Immutable reborrowing happens silently and allows multiple readers to coexist without exclusive access overhead.

Pick &mut T when you need to modify the data and want to guarantee exclusive access. Reborrowing lets you hand out temporary exclusive access without surrendering your own handle.

Trust the borrow checker to manage the temporary lifetimes. Manual lifetime management in Rust is almost always a sign that the abstraction is misaligned.

Where to go next