Reborrowing ist eines der subtilsten Konzepte in Rusts Reference-System. Es löst ein scheinbares Paradox: &mut T ist nicht Copy, aber trotzdem kannst du eine &mut-Referenz an mehrere Funktionen nacheinander übergeben, ohne sie zu „verbrauchen". Wie? Der Compiler fügt automatisch einen Reborrow ein — eine neue, kurzlebigere &mut-Referenz, die nur für die Dauer des Funktionsaufrufs existiert. Die ursprüngliche Referenz wird danach wieder nutzbar. Dieser Artikel zerlegt den Mechanismus, zeigt, wann er greift und wann nicht, und in welchen Situationen du Reborrowing explizit anwenden musst.

Das Paradox: &mut ist nicht Copy

Du erinnerst dich aus dem Shared-vs-Mut-Artikel: &T ist Copy, &mut T nicht. Trotzdem funktioniert das:

Rust Mehrfache Übergabe
fn drucken(r: &mut String) {
    println!("{r}");
}

fn modifizieren(r: &mut String) {
    r.push_str("!");
}

fn main() {
    let mut s = String::from("Hi");
    let r = &mut s;
    drucken(r);
    modifizieren(r);            // r noch nutzbar — wie das?
    drucken(r);                 // und nochmal
    println!("{r}");
}

Wenn &mut wirklich nicht Copy wäre und beim ersten drucken(r) gemoved würde, dürfte modifizieren(r) nicht mehr funktionieren. Aber es funktioniert. Warum?

Antwort: Reborrowing. Der Compiler übersetzt drucken(r) intern zu drucken(&mut *r). Das &mut *r ist ein neuer, kurzlebiger Borrow auf den gleichen Wert wie r — er lebt nur für die Dauer des Funktionsaufrufs. Nach dem Return ist die neue Referenz weg, r wird wieder „aktiviert".

Was Reborrowing genau ist

Ein Reborrow ist eine neue Referenz, die aus einer existierenden Referenz abgeleitet wird — keine Kopie, sondern ein neuer Borrow mit eigener, kürzerer Lebenszeit. Konzeptuell läuft folgender Ablauf: die Original-Referenz wird kurz „beiseitegelegt", eine neue Referenz auf denselben Wert mit einer engeren Lifetime wird erstellt, sie wird genutzt, und danach wird die Original-Referenz wieder freigegeben. Während die neue Referenz lebt, ist die Original-Referenz nicht direkt verwendbar — der Borrow Checker sorgt dafür, dass die Aliasing-XOR-Mutability-Regel auch hier eingehalten wird.

Rust Explizites Reborrow
fn main() {
    let mut s = String::from("Hi");
    let r1: &mut String = &mut s;
    let r2: &mut String = &mut *r1;     // explizites Reborrow
    r2.push_str("!");
    // r1 noch nutzbar, sobald r2 „tot" ist
    r1.push_str("?");
}

Was hier passiert:

  • r1 ist eine mutable Referenz auf s. Solange r1 aktiv ist, ist s exklusiv geborgt.
  • &mut *r1 erstellt eine neue mutable Referenz, die sich an die kurze Lebenszeit von r2 koppelt.
  • Während r2 lebt, ist r1 „eingefroren" — der Borrow Checker erlaubt keinen Zugriff über r1.
  • Wenn r2 nicht mehr verwendet wird (NLL), ist r1 wieder aktiv.

Das Schöne an Rusts Design: der Compiler fügt diese Reborrow-Operation automatisch ein, wenn du eine &mut-Referenz an eine Funktion übergibst. Du schreibst funktion(r), intern wird daraus funktion(&mut *r). Damit funktioniert die natürliche Schreibweise, ohne dass du den Mechanismus selbst kennen musst — du merkst nur, dass deine &mut-Referenz nach Funktions-Calls weiter benutzbar bleibt, obwohl &mut formal nicht Copy ist.

In sehr seltenen Fällen — etwa bei verschachtelten Closures, manchen Generic-Kontexten oder wenn die implizite Konvertierung nicht greift — musst du den Reborrow selbst schreiben. Dann hilft die Syntax &mut *r.

Implizite Reborrows bei Funktions-Calls

Rust Implizit
fn doppeln(r: &mut Vec<i32>) {
    for x in r.iter_mut() { *x *= 2; }
}

fn main() {
    let mut v = vec![1, 2, 3];
    let r = &mut v;
    doppeln(r);             // Reborrow → r überlebt den Call
    doppeln(r);             // funktioniert nochmal
    println!("{:?}", r);
}

Was der Compiler hieraus macht:

Rust Wie es intern aussieht
fn main() {
    let mut v = vec![1, 2, 3];
    let r = &mut v;
    doppeln(&mut *r);       // implizites Reborrow eingefügt
    doppeln(&mut *r);
}

Das &mut *r ist ein „Re-Borrow durch r" — neue Referenz mit eigener (kürzerer) Lifetime. Während sie aktiv ist (also für den Call), kann r nicht direkt benutzt werden. Sobald der Call endet, ist r wieder frei.

Reborrow für &T — kein Problem

Bei &T ist Reborrowing weniger sichtbar, weil &T Copy ist:

Rust Shared Reborrow
fn drucke(r: &String) {
    println!("{r}");
}

fn main() {
    let s = String::from("Hi");
    let r = &s;
    drucke(r);                  // copy (oder reborrow — ergibt dasselbe)
    drucke(r);                  // funktioniert problemlos
}

Bei &T denkst du an „Kopieren der Referenz". Bei &mut T ist es technisch ein Reborrow — aber das Resultat ist ähnlich: die Original-Referenz bleibt nutzbar.

Wann Reborrow NICHT funktioniert

Reborrowing klappt nicht immer. Klassischer Fall: wenn die Funktion die Referenz festhalten will (z. B. als Rückgabe-Lifetime an den Aufrufer):

Rust Reborrow scheitert
fn behalte(r: &mut String) -> &mut String {
    r           // Funktion gibt Referenz mit gleicher Lifetime zurück
}

fn main() {
    let mut s = String::from("Hi");
    let r1 = &mut s;
    let r2 = behalte(r1);       // r2 hat NICHT die Lifetime eines Reborrows
    // r1 ist hier ein „verschluckt" — Verwendung wäre Konflikt mit r2
    r2.push_str("!");
    // println!("{r1}");        // Fehler
}

Hier passiert kein Reborrow, sondern eine Art „Move" der &mut-Referenz. r1 ist nach dem Call praktisch nicht mehr nutzbar, weil r2 „seine Stelle eingenommen" hat.

Wann der Compiler reborrowt

Faustregel: Reborrowing passiert, wenn die Funktion keine Referenz mit der gleichen Lifetime wie der Parameter zurückgibt. Wenn die Signatur eine Rückgabe-Referenz mit der Parameter-Lifetime hat, fällt der Compiler auf die strikteren Regeln zurück.

Rust Vergleich
// Reborrow funktioniert — Funktion gibt nichts mit Lifetime des Parameters zurück:
fn fall_a(_r: &mut String) {}

// Reborrow funktioniert nicht — Funktion „nimmt" die Lifetime mit:
fn fall_b<'a>(r: &'a mut String) -> &'a mut String { r }

In Fall A wird _r nach dem Call sofort gedroppt — kein Konflikt mit dem Aufrufer-r. In Fall B trägt das Ergebnis die Lifetime des Parameters mit — der Aufrufer-r ist solange „blockiert", wie das Ergebnis lebt.

Manuelles Reborrow

Manchmal musst du Reborrow explizit machen — z. B. wenn du eine &mut-Referenz in einer Schleife oder mehrfach in einem Block weiterreichen willst:

Rust Explizit in Schleife
fn arbeite(r: &mut Vec<i32>) {
    r.push(42);
}

fn main() {
    let mut v = vec![1, 2, 3];
    let r = &mut v;
    for _ in 0..3 {
        arbeite(&mut *r);       // explizites Reborrow in der Schleife
    }
    println!("{:?}", r);
}

In manchen Fällen reicht hier auch arbeite(r) — der Compiler fügt das &mut * automatisch ein. Wenn nicht: explizit machen.

Reborrow eines Slice

Rust Slice-Reborrow
fn arbeite(s: &mut [i32]) {
    for x in s.iter_mut() { *x += 1; }
}

fn main() {
    let mut v = vec![1, 2, 3, 4];
    let s: &mut [i32] = &mut v[..];
    arbeite(&mut *s);          // explizites Reborrow
    arbeite(s);                 // auch ok — implizites Reborrow
    println!("{:?}", s);
}

Reborrow vs. Move bei Variablen-Zuweisung

Wenn du eine &mut-Referenz einer neuen Bindung zuweist, passiert typischerweise ein Move, kein Reborrow:

Rust Move vs. Reborrow
fn main() {
    let mut s = String::from("Hi");
    let r1 = &mut s;
    let r2 = r1;            // MOVE — r1 ist „verbraucht"
    // r1.push_str("!");    // Fehler — gemoved
    r2.push_str("!");
}

Bei let r2 = r1 ist die Default-Annahme: Move. r1 ist danach weg. Wenn du eine zweite Referenz brauchst, die r1 „beibehält": explizites Reborrow.

Rust Mit Reborrow
fn main() {
    let mut s = String::from("Hi");
    let r1 = &mut s;
    {
        let r2 = &mut *r1;        // Reborrow
        r2.push_str("!");
    }
    // r2 ist hier weg, r1 wieder aktiv
    r1.push_str("?");
    println!("{r1}");
}

Funktions-Calls reborrowen automatisch, Variablen-Zuweisungen moven

Das ist die wichtigste Faustregel:

  • funktion(r) mit r: &mut T → impliziter Reborrow, r bleibt nutzbar.
  • let r2 = r mit r: &mut T → Move, r ist weg.

Praxis: Reborrowing im echten Code

Mehrere Methoden-Calls auf demselben Empfänger

Rust Method-Chain
fn main() {
    let mut s = String::from("Hi");
    let r = &mut s;
    r.push_str(" ");          // impliziter Reborrow für jeden Call
    r.push_str("Welt");
    r.push_str("!");
    println!("{r}");
}

Hier siehst du Reborrowing in seiner unauffälligsten Form. Jeder r.push_str(...)-Aufruf läuft intern über die Methode String::push_str(&mut self, ...) — also nimmt sich &mut r. Ohne Reborrow wäre r nach dem ersten Call gemoved und nicht mehr verfügbar. Mit dem impliziten Reborrow ist r nach jedem Call sofort wieder frei.

Dieses Pattern ist so allgegenwärtig in Rust-Code, dass die meisten Programmierer es nie als „Reborrow" bezeichnen — es passiert einfach. Erst wenn man bewusst mit der Mechanik der Sprache arbeitet, sieht man, wie viele Sprachfeatures auf diesem unsichtbaren Mechanismus aufbauen.

Helper-Funktion im Loop

Rust Worker-Loop
fn verarbeite_einen(buffer: &mut Vec<u8>, byte: u8) {
    buffer.push(byte);
}

fn verarbeite_alle(buffer: &mut Vec<u8>, input: &[u8]) {
    for &b in input {
        verarbeite_einen(buffer, b);    // impliziter Reborrow
    }
}

fn main() {
    let mut buf = Vec::new();
    verarbeite_alle(&mut buf, &[1, 2, 3, 4]);
    println!("{buf:?}");
}

Eine klassische Schleifen-Anwendung: ein mutable Buffer wird wiederholt an eine Helper-Funktion übergeben. Ohne Reborrowing wäre der erste verarbeite_einen(buffer, b)-Call ein Move — die zweite Iteration könnte den Buffer nicht mehr verwenden. Mit dem impliziten Reborrow ist der Buffer nach jedem Aufruf wieder unangetastet verfügbar.

Wenn du jemals einen Loop schreibst, in dem du dieselbe &mut-Referenz mehrfach an eine Helper-Funktion gibst, ist Reborrowing der Grund, warum das überhaupt funktioniert. Ohne diesen Mechanismus müsste die Helper-Funktion die Referenz immer zurückgeben — was zu sehr umständlichen Signaturen führen würde.

Stream-Reader mit wiederholtem mut-Zugriff

Rust Reader-Pattern
use std::io::{self, Read};

fn lese_chunks(reader: &mut impl Read, buffer: &mut [u8]) -> io::Result<usize> {
    let mut gesamt = 0;
    loop {
        let n = reader.read(buffer)?;   // Reborrow von buffer
        if n == 0 { break; }
        gesamt += n;
    }
    Ok(gesamt)
}

Innerhalb des Loops wird buffer per Reborrow an read weitergegeben. Nach dem Call ist buffer weiter benutzbar.

Database-Statement mit Connection-Reborrow

Rust DB-Connection
struct Connection;
impl Connection {
    fn execute(&mut self, query: &str) -> usize {
        println!("EXEC: {query}");
        0
    }
}

fn migrate(conn: &mut Connection) {
    conn.execute("CREATE TABLE users (id INT)");        // Reborrow
    conn.execute("CREATE INDEX idx_users ON users(id)"); // Reborrow
    conn.execute("INSERT INTO users VALUES (1)");        // Reborrow
}

In Datenbank-APIs ist das ein typisches Pattern: eine Verbindung wird mehrfach hintereinander für verschiedene Statements verwendet. Wäre die &mut Connection ohne Reborrowing, müsste jede einzelne execute-Methode die Connection als Move nehmen und zurückgeben — die API würde so aussehen: conn = conn.execute("..."). Das ist extrem umständlich und in real-world Code unbenutzbar.

Reborrowing erlaubt die natürliche Sequenz von Statements, die jeder, der schon mal mit SQL gearbeitet hat, intuitiv schreiben würde. Bibliotheken wie sqlx, diesel und rusqlite verlassen sich alle auf diesen Mechanismus.

Mutex-Guard mit Mehrfach-Mutation

Rust Lock-Pattern
use std::sync::Mutex;

fn increment_alle(items: &Mutex<Vec<i32>>) {
    let mut guard = items.lock().unwrap();
    // guard ist eine Art &mut Vec<i32>
    guard.push(1);
    guard.push(2);
    guard.push(3);
    // Jeder Method-Call reborrowt
}

MutexGuard agiert wie &mut. Mehrere Method-Calls darauf nutzen implizites Reborrowing.

Iterator-State mit peek()

Rust Peekable-Iterator
fn parse_zahl<I: Iterator<Item = char>>(iter: &mut std::iter::Peekable<I>) -> Option<u32> {
    let mut wert = 0u32;
    while let Some(&c) = iter.peek() {       // Reborrow für peek
        if let Some(d) = c.to_digit(10) {
            wert = wert * 10 + d;
            iter.next();                      // Reborrow für next
        } else {
            break;
        }
    }
    if wert > 0 { Some(wert) } else { None }
}

peek und next auf demselben &mut Peekable<I>. Beide reborrowen implizit. Klassisches Parser-Pattern.

Nested mut-Methode auf Struct-Feld

Rust Struct-Mutation
struct App {
    log: String,
    counter: u32,
}

impl App {
    fn melde(&mut self, msg: &str) {
        self.log.push_str(msg);
        self.log.push('\n');
        self.counter += 1;
    }
}

fn main() {
    let mut app = App { log: String::new(), counter: 0 };
    app.melde("Start");          // Reborrow von app
    app.melde("Verarbeite");      // wieder Reborrow
    app.melde("Ende");
    println!("Counter: {}", app.counter);
}

Jeder app.melde(...)-Call reborrowt app. Ohne Reborrowing wäre app nach dem ersten Call weg.

State-Machine mit Reborrow

Rust State-Update
struct Spiel { score: u32 }

impl Spiel {
    fn add(&mut self, n: u32) -> &mut Self {
        self.score += n;
        self
    }
}

fn main() {
    let mut s = Spiel { score: 0 };
    s.add(10).add(20).add(30);    // Method-Chain mit Reborrow
    assert_eq!(s.score, 60);
}

Diese Variante des Builder-Patterns mit -> &mut Self ist eine elegante Anwendung von Reborrowing. Jede Methode nimmt &mut self, modifiziert das Objekt, und gibt self als &mut Self zurück — was wieder ein mutable Borrow auf dasselbe Objekt ist. Der s.add(10).add(20).add(30)-Aufruf kettet die Borrows nahtlos zusammen.

Im Vergleich zu Buildern, die self per Move nehmen und konsumieren (wie in vielen Construction-APIs), hat diese Variante den Vorteil, dass das Objekt nach der Chain noch existiert — das macht sie für State-Mutations-APIs ideal, bei denen die Identität des Objekts erhalten bleiben soll.

Besonderheiten

Reborrowing macht &mut praktisch verwendbar.

Ohne Reborrowing wäre eine &mut-Referenz nach dem ersten Funktions-Call weg — fast jede idiomatische Mutation würde Move-Probleme verursachen. Reborrowing ist der unsichtbare Mechanismus, der das natürliche Schreiben von Rust-Code ermöglicht.

&mut *r ist die Syntax für expliziten Reborrow.

*r dereferenziert die Referenz — und &mut davor erzeugt eine neue mutable Referenz auf das Ziel. Das Ergebnis ist eine kurzlebige neue Referenz, die mit r ko-existiert (während r „eingefroren" ist).

Funktions-Calls reborrowen automatisch.

Wenn du funktion(r) mit r: &mut T schreibst, fügt der Compiler intern funktion(&mut *r) ein — sofern die Funktion keine Referenz mit der Lifetime von r zurückgibt. Du musst Reborrowing nur sehr selten manuell schreiben.

let r2 = r moved, funktion(r) reborrowt.

Variablen-Zuweisung ist Move. Funktions-Übergabe ist Reborrow. Das ist der wichtigste Merksatz. Wer &mut-Konflikte hat, sollte zuerst prüfen: passiert hier ein Move (Zuweisung) oder ein Reborrow (Call)?

Reborrow funktioniert nur, wenn die Funktion die Lifetime nicht „mitnimmt“.

Wenn fn foo&lt;'a&gt;(r: &amp;'a mut T) -&gt; &amp;'a mut T einen Borrow mit gleicher Lifetime zurückgibt, ist der Aufrufer-r blockiert. Bei fn foo(r: &amp;mut T) (ohne Lifetime-Rückgabe) reborrowt der Compiler frei.

Reborrowing ist nicht „free“ — der Borrow Checker prüft trotzdem.

Während ein Reborrow lebt, ist die Original-Referenz „eingefroren". Du kannst sie nicht parallel zum Reborrow benutzen — nur sequentiell. NLL erkennt aber, dass der Reborrow am Funktions-Ende endet, sodass die Original-Referenz danach frei ist.

Method-Chaining auf &mut Self nutzt Reborrowing.

s.foo().bar().baz() mit Methoden, die &mut self nehmen und &mut Self zurückgeben, ist ein klassisches Reborrowing-Pattern. Jeder Call reborrowt die Self-Referenz für die nächste Methode.

iter_mut() in einer Schleife reborrowt.

for x in v.iter_mut() { ... } reborrowt v für die Dauer des Loops. Nach dem Loop ist v wieder voll verfügbar. Klassisches Idiom, das man oft schreibt, ohne den Mechanismus zu sehen.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

Zurück zu References & Borrowing

Zur Übersicht