References and Borrowing

On this page

Borrowing allows functions to temporarily access a value without taking ownership of its resources. &T provides shared access, &mut T provides exclusive access; references must also point to valid, aligned data that satisfies type requirements during their usage. Access permissions, lifetimes, and address conditions are all indispensable.

The "multiple readers or one writer" model is the intuitive model for ordinary data. Interior mutability allows modification under a different protocol with shared references, and exclusive references can be temporarily reborrowed, so the rules cannot be understood as "all syntactic reference variables can never coexist."

Why Borrowing Vec Elements Prevents push

push requires exclusive access to the Vec, and when capacity is insufficient, it may reallocate, invalidating the addresses of old elements. Even if capacity has been reserved, the standard push interface still requires &mut Vec, so you cannot bypass borrowing rules when you still need to use element references afterward.

Borrow element first = &values[0]When first is used later, the element must remain accessible1Last read of firstThe borrow constraint can end after this point2Modify values subsequently3Borrow constraints are inferred from subsequent usageAt this point, a non-conflicting exclusive borrow can be obtainedIf the usage of first is moved after the modification, the borrow range changes accordingly, and the program may be rejected.
fn main() {
    let mut values = vec![1, 2, 3];
    let first = &values[0];
    assert_eq!(*first, 1);
    values.push(4);
    assert_eq!(values, [1, 2, 3, 4]);
}

Moving assert_eq!(*first, 1) after push causes the borrow of first to span the modification, resulting in a compilation error. Simply writing a reference binding that is not used afterward does not mean that subsequent modifications are necessarily illegal.

NLL (Non-Lexical Lifetimes) infers the regions required for borrows based on control flow and usage constraints, rather than conservatively extending them to the end of the lexical block. It is not simply the "last line" of text scanning; branches, returned references, and destructor constraints can all affect the result. Borrow Checker Overview

Move a use across a mutation

Keep the data and push API fixed, changing only where first is used. Orange marks exclusive mutation; teal marks where later use still requires the element borrow. An overlap calls for changing use order or the interface, not merely reserving capacity.

Preparing the visual
Move a use across a mutation

In this straight-line example, reading the element reference after push conflicts even with spare capacity.

Reborrowing: Deriving a Shorter Permission Temporarily from an Existing One

fn append(values: &mut Vec<i32>) { values.push(2); }
fn main() {
    let mut values = vec![1];
    let parent = &mut values;
    {
        let child = &mut *parent;
        append(child);
    }
    parent.push(3);
    assert_eq!(parent, &[1, 2, 3]);
}

parent is the existing &mut Vec, and child is the reborrow of its target. During the time child is used, conflicting accesses cannot be made through parent; after child's borrow ends, parent can continue to be used. Ownership of the original Vec was never transferred, so this is not "ownership restored after the function returns."

Calls with known parameter types of &mut T can often be implicitly reborrowed, without needing to explicitly write &mut *parent every time. Ordinary assignment or generic consumption by value should not be universally assumed to involve reborrowing; &mut T itself is not Copy.

Explicit Reborrowing and Moving Reference Variables

fn consume<T>(_: T) {}
fn main() {
    let mut number = 1;
    let parent = &mut number;
    consume(&mut *parent); // What is passed is a newly created short borrow
    *parent += 1;
    assert_eq!(*parent, 2);
}

If consume(&mut *parent) is changed to consume(parent), the generic parameter T will be inferred as &mut i32, and the reference value itself is moved, making parent unusable afterward. Neither version moves the ownership of number; the difference is "which reference value is consumed." When fixing this, the interface should accept the actual required &mut T, or explicitly create a short borrow, rather than looking for a Clone for an exclusive reference.

Exclusive Borrows Can Be Split into Disjoint Parts

fn main() {
    let mut values = [1, 2, 3, 4];
    let (left, right) = values.split_at_mut(2);
    left[0] = 10;
    right[0] = 30;
    assert_eq!(values, [10, 2, 30, 4]);
}

It is safe for two &mut slices to exist simultaneously because the element ranges do not overlap. The compiler can also recognize independent borrows of some struct fields; for general dynamic indexing, however, you often need proven interfaces like split_at_mut rather than expecting it to deduce arbitrary arithmetic inequality conditions.

Dynamically Selecting Two Elements by First Turning Disjointness into Slice Boundaries

fn pair_mut<T>(values: &mut [T], i: usize, j: usize) -> Option<(&mut T, &mut T)> {
    if i == j || i >= values.len() || j >= values.len() { return None; }
    if i < j {
        let (left, right) = values.split_at_mut(j);
        Some((&mut left[i], &mut right[0]))
    } else {
        let (left, right) = values.split_at_mut(i);
        Some((&mut right[0], &mut left[j]))
    }
}
fn main() {
    let mut values = [10, 20, 30];
    let (a, b) = pair_mut(&mut values, 2, 0).unwrap();
    *a += 1;
    *b += 2;
    assert_eq!(values, [12, 20, 31]);
    assert!(pair_mut(&mut values, 1, 1).is_none());
    assert!(pair_mut(&mut values, 0, 3).is_none());
    assert!(pair_mut::<u8>(&mut [], 0, 0).is_none());
}

When i < j, split at j; i is definitely in the left half, and j corresponds to the zeroth item in the right half; when reversed, swap the return order so that the caller always gets i first and then j. This preserves input order while expressing mutually exclusive ranges through safe slice APIs. If the business logic only wants to swap two positions, using slice::swap directly is simpler and does not require obtaining two references first. Borrow Splitting

Method Signatures That Are Too Broad Can Also Create Unnecessary Conflicts

struct Report { title: String, rows: Vec<usize> }
fn append_row(rows: &mut Vec<usize>, title: &str) {
    rows.push(title.len());
}
fn main() {
    let mut report = Report { title: String::from("demo"), rows: Vec::new() };
    let title = &report.title;
    append_row(&mut report.rows, title);
    assert_eq!(report.rows, [4]);
}

Here, title and rows are different fields, so it is safe to share-read one and exclusive-modify the other simultaneously. If changed to a method accepting &mut Report, the call boundary declares that it might exclusively own the entire object, and the caller cannot narrow this requirement based solely on the fact that the method currently only modifies rows internally. Prioritizing narrowing auxiliary function parameters is often a more accurate expression of responsibility than adding RefCell or copying title.

Reference Variables Do Not Need mut; The Target May Still Be Modified

For let reference = &mut value;, the reference binding does not need mut, but the target can still be modified through it. let mut reference additionally allows re-binding this variable to another reference. Distinguishing between "pointer variable can be changed" and "has exclusive access to the target" helps understand signatures.

The end of a borrow does not mean the guard has been destroyed. RefCell's Ref, MutexGuard, etc., hold runtime state and usually need to exit the appropriate scope or be explicitly dropped to release the corresponding protocol state; you cannot directly apply the NLL intuition of ordinary references.

Returning References and Elision Rules

fn first_word(text: &str) -> &str {
    &text[..text.find(' ').unwrap_or(text.len())]
}
fn main() {
    let text = String::from("中文 rust");
    assert_eq!(first_word(&text), "中文");
}

The function returns a substring of the input, not a borrow of a newly created local String. find locates the byte position of the space, which happens to be on a UTF-8 boundary; you cannot arbitrarily treat character indices as byte offsets.

Here there is only one input reference lifetime, and the output is associated with it by the rules, so no explicit annotation is needed. Only when multiple inputs exist ambiguity do you need to express clearer relationships; see Lifetimes.

String: "中文 rust"Owns the text and byte bufferfirst_word(&text)Returns a &str from the same bufferUse wordSource remains readableReturning borrowed views: exclusive modification of the source is only possible after the view’s last useIf word is used later, the source cannot be modified via text.push beforehandIf independent text is truly needed, explicitly use to_owned to create a String; it changes ownership and may also allocate.

Borrowed Return Values Bring Constraints Back to the Caller

fn first_word(text: &str) -> &str {
    &text[..text.find(' ').unwrap_or(text.len())]
}
fn main() {
    let mut text = String::from("中文 rust");
    let word = first_word(&text);
    assert_eq!(word, "中文");
    text.push('!'); // word is no longer used

    let owned = first_word(&text).to_owned();
    text.clear();
    assert_eq!(owned, "中文");
}

The &str version associates the result with the source, suitable for short-term reading; the String version explicitly separates storage, suitable for usage that crosses source modifications or deallocation. The latter is not "extending the lifetime of a reference," but creating another owner responsible for the text.

Check Sequentially When Encountering Borrow Errors

IssueCorresponding Handling
Is the reference still used after modification?Move read-only consumption before modification, or change the result representation
Do the two operations actually target different fields/ranges?Narrow function parameters, use splitting interfaces
Did you move the &mut reference itself?Use explicit parameter types or explicit reborrowing
Do you need to save independent data long-term?Return owned values, explicitly cloning or sharing costs
Is it actually a guard holding a runtime lock?Limit guard scope or explicitly drop

These handling methods change access timing, access scope, or ownership relationships respectively. Using unsafe to bypass errors does not automatically make the relationships valid, and adding clone does not guarantee resource independence; you must first determine the actual contract required.

Scope of Guarantees Provided by Compile-Time Checks

The ordinary borrow checker does not track reference counts at runtime, nor does it provide business locks for arbitrary resources. &[T] and &dyn Trait also contain corresponding metadata, so it is not accurate to say that all references are fixed at 8 bytes. After entering unsafe, FFI, or interior mutability wrappers, additional contracts are maintained by the implementer; safe callers relying on these wrappers are sound.