diff --git a/diaries/rust/03 - Concepts.adoc b/diaries/rust/03 - Concepts.adoc new file mode 100644 index 0000000..8ee8908 --- /dev/null +++ b/diaries/rust/03 - Concepts.adoc @@ -0,0 +1,193 @@ +:experimental: +:docdatetime: 2022-06-14T14:33:52.489Z + += Concepts + +https://doc.rust-lang.org/book/ch03-00-common-programming-concepts.html[Link zum Buch] + + +== Variablen +=== Mutability + +Standardmäßig sind Variablen nicht mutable, also nicht veränderbar. +In anderen Sprachen ist das häufig `const` - in Rust gibt es aber auch `const`! + +Das Folgende funktioniert also nicht: + +[source, Rust] +---- +fn main() { + let x = "Hello world!"; + // Das folgende funktioniert ist, weil x nicht mutable ist! + x = "Hello Rust!"; +} +---- + +Damit Variablen mutable sind, mus `mut` genutzt werden: + +[source, Rust] +---- +fn main() { + let mut x = "Hello world!"; + // Hier funktioniert es. + x = "Hello Rust!"; +} +---- + +=== Constants + +Neben unveränderlichen Variablen gibt es auch noch Konstanten. +Diese sind sehr ähnlich zu ersteren, haben aber zwei relevante Unterschiede: + +- Der Typ *muss* angegeben werden. Type inference funktioniert hier nicht. +- Konstanten können nur auf zu Compilezeit konstante Ausdrücke gesetzt werden, keine zu Runtime veränderlichen. + +Die Konvention für Konstanten ist snake case all caps. + +Ein Beispiel dafür ist folgendes: + +[source, Rust] +---- +const MINUTES_IN_A_DAY: u32 = 24 * 60; +---- + +=== Shadowing + +Shadowing wurde beim Higher-Lower-Game schon einmal erwähnt. +Anfangs habe ich es falsch verstanden: Ich dachte Shadowing wäre, dass eine Variable unter dem selben Namen in unterschiedlichen Datentypen vorhanden wäre. + +Allerdings ist es mehr ein "Reuse" eines alten Namens. +Ein Beispiel: + +[source, Rust] +---- +fn main() { + let x = 5; + let x = x + 5; + + println!("{}", x); +} +---- + +Die Ausgabe des Programms ist dabei der letztere Wert, hier also 10. +Es ist also mehr eine neue Variable unter dem selben Namen wie die alte. +Sogar der Datentyp kann sich dabei ändern, man muss sich also nicht ständig neue Namen für Variabeln ausdenken, nur weil man sie casted (Juchuu!). + +Da Variablen immer Block-Scope-basiert (?) sind, kann dies natürlich auch in einem eingebetteten Block genutzt werden. + +Der Unterschied zu mutable Variablen ist ganz einfach: neben einigen Unterschieden unter der Haube (oder?), haben mutable Variablen einen festen Datentyp, der nicht einfach geändert werden kann. + +== Datentypen + +=== Data Inference +Jede Variable hat einen festen Datentyp. +Der Compiler kann häufig selber herausfinden, was für einer das ist, das ist die "Type Inference". +Wenn das nicht geht, muss manuell ein Typ festgelegt werden. + +Ein Beispiel: + +[source, Rust] +---- +let guess: u32 = "42".parse().expect("Not a number!"); +---- + +`"42"` ist offensichtlich ein String. +`parse()` kann verschiedene Ergebnisse-Datentypen erzeugen. +Das Ergebnis kann also verschiedene Typen haben, wir wollen ja aber wissen, was `guess` ist. +Hier muss also `guess: u32` angegeben werden, sonst gibt es einen Fehler vom Compiler. + +=== Scalar Types +Skalar heißt: ein einziges Value. +Also eine Zahl (integer/float), Boolean oder ein einzelner Character. + +==== Integer +Es signed und unsigned Integer und verschiedener Länge - 8, 16, 32, 64 und 128 Bit und "size". +"size" ist dabei architektur-abhängig, also zumeist 32 oder 64 Bit. + +- signed sind im Zweierkomplement +- man kann den Datentyp direkt an eine Zahl anhängen (`5u32`) +- man kann in Dezimalschreibweise beliebig `_` einfügen für Lesbarkeit (z.B. `1_000`) +- außerdem schreibbar in hex (`0x...`), oct (`0o...`), bin (`0b...`) oder als Byte (`b'A'`) +- Division zweier Integer erzeugt einen Integer (abgerundet) + +Overflows sind interessant: Wenn zu Debug compiled wird, gibt es ein panic und das Programm beendet mit einem Fehler (nicht auffangbar). +In Release ist es dann die "normale" Variante mit einem Wrap-around. + +Interessant ist, dass es zusätzliche Methoden für alles gibt (nicht nur `add`): + +- `wrapping_add` ersetzt das normale Addieren und wrapt +- `checked_add` wirft einen abfangbaren Fehler bei Overflow +- `overflowing_add` gibt einen Boolean, ob ein Overflow auftritt +- `saturating_add` bleibt beim Maximum oder Minimum des verfügbaren Bereiches + +[source, Rust] +---- +let number: u8 = 254; +println!("{}", number.wrapping_add(2)); +---- + +Die Ausgabe des Programms ist 0. + +==== Floats +Sind normale IEEE-754 floats mit 32 oder 64 Bit. + +==== Boolean +Auch nichts besonders, `true` oder `false` halt. + +==== Chars +Sind besonders. +Einzelne Character in Rust sind nicht einfach wie in C ein u8 unter anderem Namen, sondern wirklich ein Zeichen. +Jeder Unicode-Character ist ein Char, also auch `'🐧'`. +Chars werden mit single-quotes geschrieben (Strings mit doppelten). + +Allerdings scheint es noch ein wenig komplizierter zu sein, das kommt aber erst später. + +=== Combound Types +Gruppierung von mehreren Werten in einem Typ. + +==== Tupel +Tupel sind weird. +Sie haben eine feste Länge (wie C-Arrays), können aber verschiedene Datentypen beinhalten, also wie in Python. +Sie sind aber schreibbar, wenn `mut` zur Initialisierung genutzt wird, also nicht wie in Python. + +Ein paar Beispiele als Code: + +[source, Rust] +---- +let x: (f32, char, u8) = (1.0, '🐧', 3); +//_x.0 = 2.0; // geht nicht, da x nicht mut ist. + +let mut x: (f32, char, u8) = x; + +println!("{}", x.0); // x.0 == x[0] -> 1.0 + +// Dekonstruktur. Wie in JS wird einfach zugewiesen. +let (_a, b, _c) = x; // a = x.0 = 1.0, b = x.1 = 🐧, c = x.2 = 3 +println!("{}", b); // b is 🐧 + +x.2 = 4; // x.2 ist schreibbar, wenn x mut ist. +println!("{}", x.2); + +//x.2 = 1.0; // Das geht nicht, da x.2 ein u8 ist. +---- + +Falls eine Funktion in Rust nichts zurückgibt, gibt sie in leeres Tupel `()`, auch `unit type` genannt, zurück. + +==== Arrays +Arrays sind wie C-Arrays, haben also eine feste Länge und nur einen Datentyp. +Für "Arrays" mit veränderbarer Länge gibt es Vektoren. + +Wieder etwas Code: + +[source, Rust] +---- +let x: [i32; 5] = [1, 2, 3, 4, 5]; +// ^ so sieht der Datentyp aus + +println!("{}", x[0]); // 1, so wie immer + +let mut x = [15; 3]; // -> [15, 15, 15] +x[0] = 16; // x = [16, 15, 15] +---- + +Im Gegensatz zu C-Arrays wird allerdings vor dem Zugriff auf das Array ein Check durchgeführt. +Während C also auch außerhalb des Arrays Speicher lesen kann (mindestens theoretisch), kommt es in Rust dann zu einem Compilerfehler oder einer Runtime-Panic. diff --git a/list.json b/list.json index 9f202e5..39b2dd5 100644 --- a/list.json +++ b/list.json @@ -38,7 +38,8 @@ "entries": [ { "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": "02 - Higher-Lower-Game", "filename": "02 - Higher-Lower-Spiel"}, + { "title": "03 - Konzepte", "filename": "03 - Concepts"} ] } ] \ No newline at end of file