Ownership in Rust
Ownership (Eigentum) ist die Idee, die Rust zu Rust macht. So garantiert die Sprache einen sicheren Umgang mit dem Speicher – ohne Garbage Collector und ohne dass du Speicher von Hand freigeben musst. Am Anfang fühlt sich das ungewohnt an, aber der Kern ist einfach. Wir bauen ihn Schritt für Schritt auf.
Welches Problem Ownership löst
Abschnitt betitelt „Welches Problem Ownership löst“Während ein Programm läuft, legt es Daten im Speicher ab. Irgendwann muss dieser Speicher wieder aufgeräumt werden. Sonst belegt dein Programm immer mehr Speicher, den es gar nicht mehr braucht (ein „Speicherleck“), und stürzt irgendwann ab. Programmiersprachen lösen das meist auf eine von zwei Arten:
- Von Hand (C/C++): Du gibst den Speicher selbst frei. Vergisst du es, entsteht ein Speicherleck. Gibst du ihn zu früh frei, gibt es Abstürze und Sicherheitslücken.
- Mit Garbage Collector (Java, Go, Python): Ein Prozess im Hintergrund räumt für dich auf. Das ist bequem, kostet aber Leistung, und du weißt nie genau, wann aufgeräumt wird.
Rust geht einen dritten Weg: Schon beim Kompilieren legt es genau fest, wann jeder Wert aufgeräumt wird. Dafür hält es sich an ein paar einfache Regeln – die Regeln des Ownership. Du gibst nichts von Hand frei, und es gibt keinen Garbage Collector.
Die drei Regeln des Ownership
Abschnitt betitelt „Die drei Regeln des Ownership“Diese drei Regeln sind das ganze System. Alles andere folgt daraus:
- Jeder Wert hat genau einen Owner (Eigentümer) – die Variable, der er „gehört“.
- Es kann immer nur einen Owner gleichzeitig geben.
- Wenn der Owner seinen Gültigkeitsbereich (Scope) verlässt, wird der Wert automatisch aufgeräumt.
Schauen wir uns jede Regel in Aktion an.
Regel 3: Am Ende des Scopes wird der Wert freigegeben
Abschnitt betitelt „Regel 3: Am Ende des Scopes wird der Wert freigegeben“Ein Scope ist der Bereich zwischen { und }. Endet der Scope einer Variable, gibt Rust ihren Wert automatisch frei. Du schreibst nie free():
fn main() { { let message = String::from("hallo"); // message ist hier gültig println!("{message}"); } // <- der Scope endet, `message` wird automatisch aufgeräumt
// println!("{message}"); // ❌ Fehler: message gibt es hier nicht mehr}Du musstest nichts freigeben. Rust hat das in dem Moment erledigt, in dem message den Scope verlassen hat.
Regel 1 und 2: der „Move“
Abschnitt betitelt „Regel 1 und 2: der „Move““Jetzt wird es spannend. Sieh dir an, was passiert, wenn du eine Variable einer anderen zuweist:
fn main() { let s1 = String::from("hallo"); let s2 = s1; // das Ownership wandert von s1 zu s2 (Move)
println!("{s2}"); // ✅ funktioniert // println!("{s1}"); // ❌ Fehler: s1 wurde verschoben}Bei let s2 = s1 kopiert Rust den String nicht. Stattdessen verschiebt es das Ownership von s1 zu s2 – das nennt man einen Move. Laut Regel 2 darf es nur einen Owner geben, also ist s1 danach ungültig. Wenn du s1 trotzdem benutzt, meldet der Compiler:
error[E0382]: borrow of moved value: `s1`Übersetzt heißt das ungefähr: „Zugriff auf einen verschobenen Wert: s1“. Der Wert gehört jetzt s2, nicht mehr s1.
Und wenn ich zwei Kopien haben will?
Abschnitt betitelt „Und wenn ich zwei Kopien haben will?“Wenn du wirklich eine zweite, unabhängige Kopie brauchst, rufst du .clone() auf:
fn main() { let s1 = String::from("hallo"); let s2 = s1.clone(); // erstellt eine vollständige Kopie
println!("s1 = {s1}, s2 = {s2}"); // ✅ jetzt funktionieren beide}Dass du clone() ausdrücklich hinschreiben musst, ist Absicht: Kopieren kann teuer sein, deshalb sollst du bewusst darum bitten.
Einfache Typen werden kopiert, nicht verschoben
Abschnitt betitelt „Einfache Typen werden kopiert, nicht verschoben“Vielleicht fragst du dich, warum das hier keinen Fehler auslöst:
fn main() { let x = 5; let y = x; println!("x = {x}, y = {y}"); // ✅ beide funktionieren}Kleine Werte mit fester Größe – Ganzzahlen, Kommazahlen, Booleans und char – lassen sich billig kopieren. Deshalb kopiert Rust sie einfach, statt sie zu verschieben. Als Faustregel für den Anfang:
- Einfache Werte (Zahlen,
bool,char) → werden kopiert. Das Original bleibt gültig. - Daten mit eigenem Speicher (zum Beispiel
String) → werden verschoben. Das Original wird ungültig.
Ownership und Funktionen
Abschnitt betitelt „Ownership und Funktionen“Wenn du einen Wert an eine Funktion übergibst, wird er ebenfalls verschoben – genau wie bei einer Zuweisung an eine Variable:
fn take_ownership(s: String) { println!("Jetzt gehört mir: {s}");} // hier wird s freigegeben
fn main() { let text = String::from("hallo"); take_ownership(text); // println!("{text}"); // ❌ Fehler: text wurde in die Funktion verschoben}Genau deshalb gibt es das nächste Thema: Borrowing (Ausleihen). Damit kann eine Funktion einen Wert benutzen, ohne das Ownership zu übernehmen. So kannst du den Wert danach weiterverwenden.
Übungen
Abschnitt betitelt „Übungen“Probier jede Aufgabe zuerst selbst, im Rust Playground oder in einem lokalen Projekt. Unterwegs wirst du Compilerfehler sehen, das ist normal. Lies sie: Meistens zeigen sie direkt auf den Move.
1. Das Original behalten
Abschnitt betitelt „1. Das Original behalten“Dieses Programm lässt sich nicht kompilieren. Repariere es so, dass es Entwurf -> Entwurf v2 ausgibt, ohne eine der beiden Variablen zu entfernen.
fn main() { let original = String::from("Entwurf"); let mut edited = original; edited.push_str(" v2"); println!("{original} -> {edited}");}error[E0382]: borrow of moved value: `original` --> src/main.rs:5:16 |2 | let original = String::from("Entwurf"); | -------- move occurs because `original` has type `String`, which does not implement the `Copy` trait3 | let mut edited = original; | -------- value moved here4 | edited.push_str(" v2");5 | println!("{original} -> {edited}"); | ^^^^^^^^ value borrowed here after moveDer Compiler sagt: original wurde in Zeile 3 verschoben („value moved here“) und in Zeile 5 trotzdem noch benutzt.
Tipp: Du brauchst zwei unabhängige Strings. Dann ändert sich der eine nicht mit, wenn du den anderen änderst.
Lösung anzeigen
fn main() { let original = String::from("Entwurf"); let mut edited = original.clone(); // eine vollständige, eigene Kopie edited.push_str(" v2"); println!("{original} -> {edited}");}Ausgabe:
Entwurf -> Entwurf v2let mut edited = original; verschiebt den String, danach ist original ungültig. .clone() erzeugt einen zweiten String mit eigenem Speicher. Jetzt ändert push_str nur edited, und in original steht weiterhin Entwurf.
2. Gib es zurück
Abschnitt betitelt „2. Gib es zurück“Schreib eine Funktion add_signature, die das Ownership eines String übernimmt, " -- Ferris" ans Ende anhängt und den String zurückgibt. Ruf sie in main auf und gib das Ergebnis aus.
Tipp: Eine Funktion kann den Wert zurückgeben, den sie bekommen hat. Schreib -> String in die Signatur und nimm den Parameter als mut text: String, damit du ihn ändern darfst.
Lösung anzeigen
fn add_signature(mut text: String) -> String { text.push_str(" -- Ferris"); text // das Ownership wandert zurück zum Aufrufer}
fn main() { let letter = String::from("Bis bald!"); let letter = add_signature(letter); println!("{letter}");}Ausgabe:
Bis bald! -- FerrisMit dem Aufruf wandert das Ownership in die Funktion, mit dem Rückgabewert wandert es wieder heraus. Wir fangen es in einer neuen Variable letter auf (sie überdeckt die alte, das nennt man Shadowing). Das funktioniert, aber Werte ständig hinein- und herauszureichen wird schnell mühsam. Genau dieses Problem löst Borrowing.
3. Wem gehört es jetzt?
Abschnitt betitelt „3. Wem gehört es jetzt?“Dieses Programm lässt sich nicht kompilieren. Repariere es so, dass es zuerst Ana steht jetzt auf der Liste und dann ["Ana"] ausgibt.
fn main() { let mut names = Vec::new(); let name = String::from("Ana"); names.push(name); println!("{name} steht jetzt auf der Liste"); println!("{:?}", names);}error[E0382]: borrow of moved value: `name` --> src/main.rs:5:16 |3 | let name = String::from("Ana"); | ---- move occurs because `name` has type `String`, which does not implement the `Copy` trait4 | names.push(name); | ---- value moved here5 | println!("{name} steht jetzt auf der Liste"); | ^^^^ value borrowed here after moveTipp: push übernimmt das Ownership, genau wie ein Funktionsaufruf. Wem gehört der String nach Zeile 4? Benutz name entweder vor dem Move, oder frag den neuen Owner.
Lösung anzeigen
fn main() { let mut names = Vec::new(); let name = String::from("Ana"); println!("{name} steht jetzt auf der Liste"); // benutzen, solange er uns noch gehört names.push(name); // jetzt gehört er dem Vektor println!("{:?}", names);}Ausgabe:
Ana steht jetzt auf der Liste["Ana"]Nach names.push(name) ist der Vektor der Owner, und name lässt sich nicht mehr benutzen. Zuerst auszugeben ist eine Lösung. Du kannst auch die ursprüngliche Reihenfolge behalten und stattdessen names[0] ausgeben, denn der Vektor hat den String ja noch. Klonen würde ebenfalls funktionieren, aber hier musst du nichts kopieren.
Zusammenfassung
Abschnitt betitelt „Zusammenfassung“- Mit Ownership verwaltet Rust den Speicher sicher – ganz ohne Garbage Collector.
- Drei Regeln: Jeder Wert hat einen Owner, es gibt immer nur einen Owner gleichzeitig, und der Wert wird freigegeben, sobald der Owner seinen Scope verlässt.
- Wenn du Daten mit eigenem Speicher (wie
String) zuweist, werden sie verschoben (Move) – das Original wird ungültig. - Mit
.clone()bekommst du eine unabhängige Kopie. Einfache Typen (Zahlen,bool,char) werden automatisch kopiert. - Auch beim Übergeben an eine Funktion wird ein Wert verschoben – und das führt uns direkt zum Borrowing.
Kurzer Check
Abschnitt betitelt „Kurzer Check“Kurzer Check
1. Wie viele Owner kann ein Wert in Rust gleichzeitig haben?
2. Welche Methode rufst du auf, um eine unabhängige Kopie eines Strings zu erstellen, statt ihn zu verschieben?
3. Welcher dieser Typen wird bei einer Zuweisung an eine andere Variable automatisch kopiert statt verschoben?
4. Was passiert, wenn du einen String an eine Funktion übergibst, die ihn als Wert (nicht als Referenz) annimmt?
5. Wann räumt Rust einen Wert laut dieser Seite automatisch auf (Drop)?
Punkte: 0 / 5
Weiterlesen
Abschnitt betitelt „Weiterlesen“- Was ist Eigentümerschaft? – Das Rust-Buch: das offizielle Kapitel, mit Bildern, wie ein
Stringim Speicher aussieht und warum ein Move ihn nicht kopiert. - Ownership and moves – Rust by Example (auf Englisch): kurze, ausführbare Beispiele für Moves, auch für Moves in Funktionen hinein.
- RAII – Rust by Example (auf Englisch): wie Werte aufgeräumt werden, wenn sie ihren Scope verlassen, und welche Rolle der Trait
Dropdabei spielt. Copy– Doku der Standardbibliothek (auf Englisch): welche Typen kopiert statt verschoben werden, und der Unterschied zwischenCopyundClone.- Rust-Track auf Exercism (auf Englisch): kostenlose Übungsaufgaben mit Feedback, wenn du mehr üben willst, als diese Seite bietet.