Rust – Was ist Borrowing?
Borrowing (Leihen) ist eines der zentralen Konzepte in Rusts Speichermodell und eng mit dem Ownership-System verknüpft. Anstatt Daten zu verschieben oder zu kopieren, können Daten durch Borrowing geliehen werden, was eine sichere und effiziente Speicherverwaltung ermöglicht.
Bernd Fischer
14. Okt 2024 · 3 Minuten Lesezeit
Borrowing ist ein mächtiges Konzept in Rust, das es erlaubt, Daten sicher zu leihen, ohne den Besitz zu übertragen. Es ermöglicht es, Speicher effizient zu verwalten und gleichzeitig sicherzustellen, dass keine ungültigen Referenzen entstehen. Die Unterscheidung zwischen unveränderlichem und veränderlichem Borrowing hilft dabei, die Datenkonsistenz zu gewährleisten und Datenraces zu verhindern.
1. Das Ownership-System und Borrowing
In Rust besitzt jede Variable standardmäßig ihren Speicherbereich. Das bedeutet, wenn eine Variable erstellt wird, übernimmt sie den "Besitz" (Ownership) der Daten. Manchmal möchte man jedoch nur eine Referenz auf die Daten einer anderen Variable haben, ohne das Ownership zu übernehmen. Das nennt man Borrowing.
Beispiel: Borrowing mit Referenzen
fn main() {
let s1 = String::from("Hello");
let s2 = &s1; // s2 "leiht" sich den Wert von s1
println!("{}, {}", s1, s2);
}
In diesem Beispiel wird eine Referenz (&s1) verwendet, um Borrowing zu ermöglichen. Das bedeutet, dass s2 eine unveränderliche Referenz auf s1 ist, aber kein Eigentum an den Daten hat.
2. Unveränderliches Borrowing
Rust unterscheidet zwischen unveränderlichem und veränderlichem Borrowing. Wenn eine unveränderliche Referenz (&T) verwendet wird, darf der Wert nicht verändert werden, solange diese Referenz aktiv ist.
Beispiel: Unveränderliche Referenz
fn main() {
let s = String::from("Rust");
let r = &s; // unveränderliches Borrowing
println!("Referenz: {}", r);
}
Hier kann s nicht verändert werden, solange die Referenz r besteht. Unveränderliche Referenzen sind mehrfach erlaubt, solange der Wert nicht verändert wird.
3. Veränderliches Borrowing
Ein veränderliches Borrowing ermöglicht es, den Wert zu ändern. Allerdings erlaubt Rust immer nur eine veränderliche Referenz gleichzeitig. Diese Regel verhindert Datenraces und stellt sicher, dass nur eine Stelle den Wert verändern kann.
Beispiel: Veränderliches Borrowing
fn main() {
let mut s = String::from("Hello");
let r = &mut s; // veränderliches Borrowing
r.push_str(", world!");
println!("Verändert: {}", r);
}
In diesem Beispiel kann r den Wert von s verändern. Beachte, dass s als mut deklariert werden muss, um eine veränderliche Referenz zu erhalten.
4. Regeln für Borrowing
Rust hat klare Regeln für Borrowing, die sicherstellen, dass es zu keinen Speicherfehlern kommt:
- Mehrere unveränderliche Referenzen sind erlaubt, aber es darf nur eine veränderliche Referenz geben.
- Unveränderliche und veränderliche Referenzen dürfen nicht gleichzeitig existieren.
Beispiel: Regelverstoß
fn main() {
let mut s = String::from("Rust");
let r1 = &s; // unveränderliches Borrowing
let r2 = &mut s; // veränderliches Borrowing - Fehler!
println!("{}, {}", r1, r2);
}
In diesem Fall entsteht ein Kompilierfehler, da unveränderliche und veränderliche Referenzen nicht gleichzeitig existieren dürfen.
5. Lebensdauern (Lifetimes) und Borrowing
Um sicherzustellen, dass eine Referenz nicht länger lebt als der Wert, auf den sie zeigt, verwendet Rust Lebensdauern (Lifetimes). Lebensdauern stellen sicher, dass keine ungültigen Referenzen existieren.
Beispiel: Lebensdauer einer Referenz
fn main() {
let s1 = String::from("Hello");
let r;
{
let s2 = String::from("World");
r = &s2; // Fehler: s2 lebt nicht lange genug
}
println!("Referenz: {}", r);
}
Dieser Code erzeugt einen Fehler, weil s2 in einem inneren Block deklariert wird und nach diesem Block nicht mehr existiert. Rust verhindert, dass r auf eine ungültige Referenz zeigt.