From 47a1bdb9a61efe77015e595379eb2063d13c4f0b Mon Sep 17 00:00:00 2001 From: Daniel Kluge Date: Tue, 5 Jul 2022 15:32:40 +0200 Subject: [PATCH] Rust Ownership --- diaries/rust/04 - Ownership.adoc | 151 +++++++++++++++++++++++++++++++ list.json | 3 +- 2 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 diaries/rust/04 - Ownership.adoc diff --git a/diaries/rust/04 - Ownership.adoc b/diaries/rust/04 - Ownership.adoc new file mode 100644 index 0000000..785ec6b --- /dev/null +++ b/diaries/rust/04 - Ownership.adoc @@ -0,0 +1,151 @@ +:experimental: +:docdatetime: 2022-07-05T15:32:40+02:00 + += Ownership + +https://doc.rust-lang.org/book/ch04-00-understanding-ownership.html[Link zum Buch] + +== Was ist das? + +Jeder Wert hat eine Variable, die ihn "besitzt". +Jeder Wert kann zu einem Zeitpunkt nur von _einer_ Variable bessessen werden. +Sollte die Variable aus dem Scope verschwinden, wird der Wert ungültig und aus dem Speicher entfernt. + +== Warum? + +Wenn ein Wert eine feste Länge hat, kann man sie ganz einfach auf den Stack packen. +Falls die Länge aber variabel ist, muss zu Laufzeit Speicher allokiert werden. +In C ist das dann der Aufruf `malloc`, der einen Pointer zu dem entsprechenden Bereich gibt. +Später muss dann `free` aufgerufen werden, um den Speicher wieder freizugeben, weil C keinen Garbage Collector hat, der sich alleine darum kümmert. + +Es ist wenig verwunderlich, dass beim manuellen Aufruf von `malloc` und `free` allerhand schiefgehen kann. +Entweder kann Speicher zu früh (eventuell falsche Werte) oder zu spät (höherer Speicherverbrauch) freigegeben werden, oder auch zum Beispiel doppelt. + +Rust nutzt deshalb (wenn man das nicht aktiv anders macht) einen anderen Ansatz, bei dem der Compiler selber `drop` (was in etwa `free` entspricht) einfügt, wenn eine Variable aus dem Scope verschwindet. + +== Was das für den Code bedeutet + +=== String Datentyp + +Fangen wir mal mit einem Datentypen an, den das betrifft. + +Neben String literals gibt es auch noch einen anderen String-Typen, Rust scheint da sich ein wenig an OOP zu orientieren. +Im Higher-Lower-Game wurde der auch schon benutzt, und User-Input mit `String::from(x)` in eine Variable gelegt. +Dieser String-Typ hat den Vorteil, dass er eine dynamische Länge hat und damit verändert werden kann. + +Ein Beispiel: + +[source, Rust] +---- +let mut x = String::from("Hello"); // Legt "dynamischen" String an +x.push_str(" world!"); // Konkatiniert an den String + +println!("{}", x); // "Hello world!" +---- + +Das geht mit den normalen String-Literalen (`let mut x = "Hello";`) nicht, da diese eine immer eine feste Länge haben. +Theoretisch kann `x` natürlich dann überschrieben werden, mit einem String anderer Länge, aber anscheinend wird das von Rust überdeckt und wahrscheinlich ähnlich wie Shadowing gehandhabt. + +=== Move + +[source, Rust] +---- +let x = 5; // Int -> feste Größe und auf Stack +let y = x; + +let s1 = String::from("Hello world"); // Dynamischer String auf Heap +let s2 = s1; +---- + +Hier trifft ähnliches zu, wie zum Beispiel in Python: primitive Datentypen, wie `int` oder `float`, werden einfach kopiert, wenn sie einer anderen Variable zugewiesen werden. +Bei Objekten auf dem Heap dagegen, wird auch kopiert, allerdings nur was wirklich in `s1` steht: die Referenz auf den Speicher (also ein Pointer), die Länge und andere "Metadaten". + +In Sprachen mit Garbage Collector also Java oder Python haben `s1` und `s2` jetzt zu jeder Zeit den gleichen Wert. +Sollte eines verändert werden, wird das zweite auch verändert. +Sehr tückisch manchmal. + +Rust löst es anders: Damit nicht zum Beispiel ein doppeltes `free` auftritt, wird hier `s1` invalidiert, nachdem es in eine andere Variable gegeben wurde. +Wie oben beschrieben: Der Wert von `s1` kann nur einen Besitzer haben und der wird mit der letzten Zeile gewechselt. +Sollte man nach dem Snipped ein print nutzen, gäbe es einen Compile-Fehler. + +Natürlich gibt es auch Wege für ein "deep copy", allerdings ist das nie der Standard. +Die Methode dafür (muss natürlich implementiert sein) heißt `clone`. + +Wir könnten also auch schreiben `let s2 = s1.clone()` und beide Variablen wären unabhängig voneinander und gültig. +Das kann aber sehr teuer für die Laufzeit sein! + +=== Copy und Drop Annotation + +Im Buch wird jetzt noch kurz angeschnitten, dass diese primitiven Datentypen kopiert werden, weil sie das `Copy` "trait" implementiert hätten. +An dem Punkt habe ich noch keine Ahnung, was das ist, aber ich denke es wird so ähnlich sein, wie Java Interfaces? + +Wenn ein Datentyp den `Copy` trait hat, wird es auf jeden Fall einfach kopiert, statt gemoved. + +Es gibt auch ein `Drop` trait, mit dem noch irgendwas ausgeführt werden kann, wenn ein Wert dieses Types gedropped wird. Dieser trait ist exklusiv zu `Copy`. + +== In Funktionen + +Sollte eine Funktion eine Variable übergeben bekommen, wird auch das Ownership der Variable dahin übergeben. +Nach Ausführen der Funktion ist die Variable ungültig. +Der Wert wird aber möglicherweise wieder zurückgegeben. +Das gilt natürlich nicht für die `Copy`-Datentypen. + +Wie vorher schon erfahren, kann man auch Referenzen und mutable Referenzen übergeben, wodurch die Variable nur "geborgt" wird. + +In C/C++ gibt es ja beim Aufruf von zum Beispiel Funktionen eines Structs oder Objekt, den Pfeil (`x->fun()`) der quasi auch nur ein hübsches `(*x).fun()` ist. + +In Rust sag ich der Funktion, dass ein Argument eine Referenz auf einen Datentypen ist, und ich kann mit der Variable arbeiten, als wäre sie da. +Wenn ich den Wert der Referenz haben will, muss ich sie natürlich immer noch dereferenzieren. + +Solange die Referenz nicht mutable ist, können davon unendlich viele existieren. + +Mutable References werden noch wieder kritisch behandelt - es kann zu einem Zeitpunkt immer nur eine mutable Referenz geben (ähnlich Ownership also). +Noch krasser: Eine mutable Referenz kann nicht erstellt werden, solange eine immutable existiert. +"Existieren" bedeutet hier natürlich: Wenn die immutable Referenz nach Erstellen der mutable Referenz noch einmal genutzt wird. +Sonst kümmert sich der Compiler drum. + +Das heißt natürlich auch, dass alle immutable Referenzen invalid werden, sobald eine mutable Referenz erstellt wird. + +Damit werden (unter anderem) Race Conditions schon beim Compilen verhindert. + +=== Dangling references + +[source, Rust] +---- +fn dangle() -> &String { + let s = String::from("hello"); + &s // Referenz auf s returned +} // Hier fliegt s aus dem Scope +---- + +Hier ist eine Funktion gebaut, die nur eine Referenz zurückgibt. +Allerdings wird `s` ja (da nach Funktion out of scope) nach der Funktion gedropped. +Der Compiler gibt uns dafür auch einen Fehler. + +Das Tutorial sagt an diesem Punkt, dass man am besten keine Referenzen zurückgibt, die Fehlermeldung redet aber auch noch von "lifetimes" und dass `&'static String` ein möglicher Rückgabetyp wäre. +Das kommt wohl aber erst später... + +== Der Slice-Datentyp + +Wenn wir auf Arrays arbeiten, wäre es ja cool, an verschiedenen Stellen gleichzeitig zu arbeiten. +Nur so kann multithreading etc. funktionieren. + +Dafür hat Rust den Slice-Datentyp. +Der funktioniert ähnlich wie Array-Ranges in Python. + +[source, Rust] +---- +let s = String::from("hello world"); + +let hello = &s[0..5]; +let world = &s[6..11]; +---- + +Rust kümmert sich dabei darum, dass wir jetzt keinen Unsinn mehr mit `s` machen. +Sollte man versuchen `s` zu mutaten und danach die Slice zu nutzen, gibt es einen Fehler, denn Slices sind genauso Referenzen. + +Fun fact: String Literale sind auch Slices und damit Referenzen von Strings. +Noch mehr fun fact: Da dynamische String und String Literale damit quasi den selben Typ beschreiben, haben sie auch den gemeinsamen Typ `&str`. +Für Leseoperationen kann also im Allgemeinen dieser benutzt werden. + +Slices können auch mutable sein, dafür muss aber das ursprüngliche Array mmutable sein und es kann immer nur ein mutable Slice gleichzetig existieren (also genauso wie beim Ownership). diff --git a/list.json b/list.json index bc8f707..d10b2b2 100644 --- a/list.json +++ b/list.json @@ -39,7 +39,8 @@ { "title": "00 - Hello World", "filename": "00 - Hello World"}, { "title": "01 - Cargo", "filename": "01 - Cargo"}, { "title": "02 - Higher-Lower-Game", "filename": "02 - Higher-Lower-Spiel"}, - { "title": "03 - Cheatsheet", "filename": "03 - Concepts"} + { "title": "03 - Cheatsheet", "filename": "03 - Concepts"}, + { "title": "04 - Ownership", "filename": "03 - Ownership"} ] }, {