Teil 3: Methoden, Optionen, Verweise und Kopfknoten
Die letzten 2 Tage und auch heute habe ich mich mit verschiedenen Themen auseinandergesetzt. Da ich irgendwie einem Thema ins andere gerutscht bin, hat es wenig Sinn gemacht einzelne Beiträge zu schreiben. Ich musste erst einmal den Kopfknoten lösen, der mit den verschiedenen Themen einher ging 😇.
Ich habe mehrere Beispiele zusammengestellt, die ganz gut das wiederspiegeln, was ich in den letzten Tagen gelernt habe. Aber fangen wir einfach mal an.
Rust, Referenzierung und Ownership
Ich will jetzt hier nicht zu weit ins Detail gehen. Ich denke, dass das Rust Buch das alles viel besser im Detail erklären kann als ich. Nur soviel: Was man aus Sprachen wie Go oder C++ als Pointer kennt funktioniert in Rust anders. Rust sorgt durch sein Owenership System dafür, dass jeder Wert nur einen einzigen Besitzer hat. Werte können aber dennoch refrenziert werden. Dazu werden sie “ausgeliehen” und anschließend an den Besitzer zurückgegeben.
Fangen wir einfach mal mit einem Beispiel an. Den kompletten Code findet ihr hier:
Lifetimes
Ich habe in meinem Beispiel eine Datenstruktur für eine Person erstellt. Diese Person soll einen Namen und eine Liste Freunden haben. Den Namen habe ich als &str string slice definiert, was eine Referenz auf den Speicherort ist. Wer sich für Details zuma Strings interessiert sollte diesen Artikel lesen. Hier wird genau erklärt die Strings gespeichert und referenziert werden und was die UNterschiede sind.
|
|
Hier liefert uns der Compiler einen Fehler:
|
|
Rust möchte eine Lifetime Spezifizierung haben. Das leigt daran, dass Rust nicht weiß wie lange die &str Verweise gültig sein sollen. Standardmäßig wird in Rust nach verlassen einen Blocks “{}” der Speicher freigegeben und damit würden diese Verweise ungültig. Über die Lifetimes teilen wir dem Rust Compiler mit wie Lange die Verweise gültig sein sollen:
|
|
Wir deklarieren hier eine Lifetime a für unsere Datenstruktur. Diese weisen wir auch dem &str Verweis des Namens und den Elementen der Freundesliste zu. Dadurch sagen wir dem Compiler, dass die Felder so lange existieren sollen, wie die Person existiert.
Bei der Implementierung der Methoden fordert der Compiler ebenfalls wieder Lifetimes:
|
|
Hier gibt es nur zwei einfache Methoden. new erstellt eine neue Person und ist genau genommen keine Methode. Die zweite Methode fügt Freunde zur Freundesliste hinzu.
match und if let
Das Strukturfeld friends ist ein Feld vom Typ Option, d.h. das Feld kann entweder eine Liste mit Freunden enthalten oder nichts (None). Wenn man den Inhalt des Feldes nutzen möchte muss man meistens unterscheiden ob das Feld belegt ist oder nicht. Dabei hilf match:
|
|
Für den Fall, dass friends Daten enthält wird Peter has friends ausgegeben, andernfalls passiert gar nichts.
Das Gleiche erreicht man kompakter indem man den match Block durch if let ersetzt:
|
|
Beide Varianten führen zum gleichen Ergebnis. In diesem Fall sind aber keine Freunde vorhanden. Ist bei einer Person das Optionsfeld belegt erfolgt auch eine Ausgabe:
|
|
Auch hier ist die Verwendung if let kompakter, wobei ich persönlich finde, dass das der match Block klarer zu verstehen gibt, was hier getan wird.
Referenzierung und Veränderung Daten
Nehmen wir mal an wir möchten in dem oben stehenden Beispiel einen Freund ändern. In diesem Fall muss die Freundesliste mutable / änderbar sein:
|
|
Durch die Verwendung der Methode as_mut() wird unsere Freundesliste an had_friends als mutable übergeben und wir können das 2. Element ändern. Wenn wir das so machen tritt aber ein anderes Problem auf. has_friends wird neuer Eigentümer der Freundesliste. D.h., wenn wir auf max.friends zugreifen wollen meldet der Compiler einen Fehler:
|
|
Fehlermeldung:
|
|
Der Compiler sagt uns, dass der Inhalt max_friends zu has_friends verschoben wurde. Außerdem gibt der Compiler an, dass der Copy Trait vom Typ Vector nicht implementiert wird. Gleichzeitig schlägt uns der Compiler einen Teil der Lösung vor. Wir müssen max.friends an has_friends ausleihen.