Add Rust chapter
This commit is contained in:
parent
2115dbb8f0
commit
efc6f54424
193
diaries/rust/03 - Concepts.adoc
Normal file
193
diaries/rust/03 - Concepts.adoc
Normal file
@ -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.
|
@ -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"}
|
||||
]
|
||||
}
|
||||
]
|
Loading…
Reference in New Issue
Block a user