Einleitung: Ein kleiner Buchstabe, großer Unterschied
\n\nHallo, zukünftiger Rust-Entwickler! Wenn du bereits begonnen hast, Rust zu lernen, ist dir sicherlich aufgefallen, dass der Compiler ein strenger, aber fairer Lehrer ist. Er verzeiht keine Tippfehler und schon gar keine Verwechslungen in der Syntax. Eine der häufigsten Fallen, in die Anfänger (und nicht nur sie) tappen, ist die Verwechslung von self und Self. Es scheint nur ein Buchstabe Unterschied zu sein – groß oder klein. Aber hinter diesem Buchstaben verbirgt sich ein grundlegender Unterschied zwischen einer Instanz und einem Typ.
Auf Stack Overflow hat diese Frage tausende Aufrufe, und das nicht ohne Grund. Heute werden wir ein für alle Mal klären, worin der Unterschied zwischen self und Self in Rust besteht. Wir werden nicht nur eine langweilige Definition lesen, sondern ein Schritt-für-Schritt-Tutorial mit lebendigen Beispielen durchgehen und ein paar kleine Programme schreiben. Am Ende des Artikels wirst du den Unterschied nicht nur kennen, sondern ihn intuitiv verstehen. Los geht's!
1. self: „Ich“ – das ist die Instanz
\n\nBeginnen wir mit self. In Rust wird self, wie in vielen anderen Sprachen auch, verwendet, um die aktuelle Instanz einer Struktur oder Enumeration zu bezeichnen. Es ist das Äquivalent zu this in C++, Java, JavaScript oder Python. Wenn du eine Methode schreibst, die auf einem bestimmten Objekt aufgerufen wird, verwendest du self.
Es gibt drei Hauptformen von self in der Methodensignatur:
- \n
self– übernimmt den Besitz der Instanz (consumes the instance). Nach dem Aufruf der Methode ist das ursprüngliche Objekt nicht mehr verfügbar. \n &self– eine unveränderliche Referenz auf die Instanz (der häufigste Fall). Die Methode kann Daten lesen, aber nicht ändern. \n &mut self– eine veränderliche Referenz auf die Instanz. Die Methode kann die Felder der Struktur ändern. \n
Lass uns ein einfaches Beispiel schreiben. Stell dir vor, wir erstellen eine Struktur Student.
struct Student {\n name: String,\n kurs: u32,\n}\n\nimpl Student {\n // Methode, die &self (unveränderliche Referenz) nimmt\n fn vorstellen(&self) {\n println!("Hallo! Ich bin {}, studiere im {} Semester.", self.name, self.kurs);\n }\n\n // Methode, die &mut self (veränderliche Referenz) nimmt\n fn naechstes_semester(&mut self) {\n self.kurs += 1;\n println!("{} ist jetzt im {} Semester!", self.name, self.kurs);\n }\n\n // Methode, die self (Besitz) nimmt\n fn studium_beenden(self) {\n println!("{} hat das Studium im {} Semester abgeschlossen. Tschüss!", self.name, self.kurs);\n // self wird hier zerstört, der Speicher wird freigegeben\n }\n}\n\nfn main() {\n let mut student = Student {\n name: String::from("Anna"),\n kurs: 2,\n };\n\n student.vorstellen(); // &self\n student.naechstes_semester(); // &mut self\n student.studium_beenden(); // self (Besitz)\n\n // Nach dem Aufruf von studium_beenden ist die Variable 'student' nicht mehr verfügbar!\n // Kommentiere die folgende Zeile aus, um einen Kompilierungsfehler zu sehen:\n // student.vorstellen(); // FEHLER: borrow of moved value\n}\n\nBeachte den letzten Kommentar. Wenn wir self (ohne Referenz) übergeben, geben wir den Besitz ab. Dies ist ein mächtiges Werkzeug, das eine präzise Kontrolle über die Lebensdauer eines Objekts ermöglicht. In anderen Sprachen (z. B. Python) gibt es diese Kontrolle nicht – das Objekt lebt, solange Referenzen darauf existieren. In Rust entscheidest du selbst, wann ein Objekt „konsumiert“ wird.
2. Self: „Ich“ – das ist der Typ
\n\nUnd jetzt kommt das Interessanteste. Self (mit großem Anfangsbuchstaben) ist ein Alias für den Typ, für den wir Methoden implementieren. Einfach ausgedrückt: Self innerhalb eines impl-Blocks wird durch den Namen der Struktur oder Enumeration ersetzt, mit der wir arbeiten.
Wozu ist das gut? Erstens ist es praktisch. Anstatt den vollständigen Namen der Struktur zu schreiben, schreibst du Self. Zweitens ist es entscheidend