Zum Inhalt springen

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.

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.

Diese drei Regeln sind das ganze System. Alles andere folgt daraus:

  1. Jeder Wert hat genau einen Owner (Eigentümer) – die Variable, der er „gehört“.
  2. Es kann immer nur einen Owner gleichzeitig geben.
  3. 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
}
▶ Selbst ausprobieren

Du musstest nichts freigeben. Rust hat das in dem Moment erledigt, in dem message den Scope verlassen hat.

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
}
▶ Selbst ausprobieren

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.

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
}
▶ Selbst ausprobieren

Dass du clone() ausdrücklich hinschreiben musst, ist Absicht: Kopieren kann teuer sein, deshalb sollst du bewusst darum bitten.

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.

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.

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.

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` trait
3 | let mut edited = original;
| -------- value moved here
4 | edited.push_str(" v2");
5 | println!("{original} -> {edited}");
| ^^^^^^^^ value borrowed here after move

Der 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 v2
▶ Selbst ausprobieren

let 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.

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! -- Ferris
▶ Selbst ausprobieren

Mit 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.

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` trait
4 | names.push(name);
| ---- value moved here
5 | println!("{name} steht jetzt auf der Liste");
| ^^^^ value borrowed here after move

Tipp: 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"]
▶ Selbst ausprobieren

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.

  • 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

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