Was ist ein Garbage Collector und warum hat Rust darauf verzichtet?
\n\nWenn Sie aus der Welt von Python, Java oder C# kommen, sind Sie es gewohnt, dass der Speicher von einem Garbage Collector (GC) verwaltet wird. Dies ist ein unsichtbarer „Reiniger“, der regelmäßig prüft, welche Objekte nicht mehr verwendet werden, und den von ihnen belegten Speicher freigibt. Bequem? Ja. Aber es gibt Nuancen:
\n\n- \n
- Unvorhersehbare Pausen. Der GC kann im ungünstigsten Moment starten – wenn Ihr Code eine wichtige Anfrage verarbeitet oder einen Frame in einem Spiel rendert. \n
- Speicher-Overhead. Der GC benötigt oft zusätzlichen Speicher für seine Arbeit. \n
- Komplexität in Echtzeitsystemen. In Betriebssystemen, Treibern oder eingebetteten Geräten sind GC-Pausen inakzeptabel. \n
Rust bietet einen radikal anderen Ansatz. Anstelle eines Garbage Collectors wird das Ownership-System verwendet, das zur Kompilierzeit arbeitet. Der Compiler entscheidet selbst, wann Speicher freigegeben werden muss, und tut dies ohne jeglichen Runtime-Overhead. Lassen Sie uns verstehen, wie das funktioniert.
\n\nDas Ownership-System – die Grundlage der Speicherverwaltung
\n\nIn Rust hat jeder Wert genau einen Besitzer (Owner) – eine Variable, der dieser Wert gehört. Wenn der Besitzer den Gültigkeitsbereich (Scope) verlässt, wird der Speicher automatisch freigegeben. Kein Garbage Collector, keine Referenzzählung – nur der Compiler und strenge Regeln.
\n\nDie Ownership-Regeln in Rust
\n\nHier sind die drei Hauptregeln, die der Compiler zur Kompilierzeit prüft:
\n\n- \n
- Jeder Wert in Rust hat einen Besitzer. \n
- Wenn der Besitzer den Gültigkeitsbereich verlässt, wird der Wert gelöscht (drop). \n
- Ein Wert kann an einen anderen Besitzer übergeben (move) oder ausgeliehen (borrow) werden. \n
fn main() {\n let s1 = String::from("Hallo"); // s1 – Besitzer des Strings\n let s2 = s1; // Das Eigentum GEHT an s2 über (move)\n // println!("{}", s1); // Fehler! s1 besitzt den String nicht mehr\n println!("{}", s2); // Ok: s2 ist der neue Besitzer\n} // Hier verlassen s1 und s2 den Gültigkeitsbereich,\n // aber der Speicher wird nur einmal freigegeben – für s2\n\nIn Python oder Java würden Sie einfach eine weitere Referenz auf das Objekt erstellen, und der GC würde sich später darum kümmern. In Rust – keine Überraschungen: Der Compiler garantiert, dass der Speicher genau einmal freigegeben wird.
\n\nBorrowing – Daten nutzen, ohne das Eigentum zu übertragen
\n\nOft müssen wir vorübergehend auf Daten zugreifen, ohne sie dem Besitzer wegzunehmen. Dafür gibt es in Rust Referenzen (references). Es gibt zwei Arten:
\n\n- \n
- Unveränderliche Referenzen (
&T): Man kann Daten lesen, aber nicht ändern. \n - Veränderliche Referenzen (
&mut T): Man kann lesen und ändern, aber nur, wenn keine anderen aktiven Referenzen vorhanden sind. \n
fn main() {\n let mut text = String::from("Rust");\n \n let r1 = &text; // unveränderliche Referenz\n let r2 = &text; // noch eine unveränderliche – erlaubt\n println!("{} {}", r1, r2);\n \n let r3 = &mut text; // veränderliche Referenz – funktioniert, da r1 und r2 nicht mehr verwendet werden\n r3.push_str(" rocks!");\n println!("{}", r3);\n \n // So geht es nicht:\n // let r1 = &text;\n // let r3 = &mut text; // Fehler! Man kann keine veränderliche und unveränderliche Referenz gleichzeitig haben\n}\n\nDer Rust-Compiler ist nicht nur ein statischer Analysator, sondern ein strenger, aber fairer Lehrer. Er lässt Sie nicht ins offene Messer laufen. Wenn Sie versuchen, eine veränderliche Referenz zu erstellen, während unveränderliche existieren, erhalten Sie einen Kompilierungsfehler. Das ist die Arbeit des Borrow Checkers – des Mechanismus, der den