: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, 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.