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:

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: der Compiler fügt diese Reborrow-Operation automatisch ein, wenn du eine &mut-Referenz an eine Funktion übergibst.

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}");
}

Jeder Method-Call auf &mut self reborrowt implizit. Ohne Reborrowing wäre r nach dem ersten Aufruf weg.

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:?}");
}

Der buffer-Parameter wird in der Schleife jedes Mal an verarbeite_einen reborrowt. Nach dem Call ist buffer wieder voll verfügbar.

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
}

Drei sequentielle execute-Calls auf &mut Connection. Ohne Reborrowing wäre nach dem ersten Call die Connection „verbraucht" — Reborrowing macht die natürliche Sequenz möglich.

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);
}

-> &mut Self ermöglicht Method-Chaining mit Self-Return. Jeder Call reborrowt das eingehende &mut self für die nächste Methode.

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