diff --git a/diaries/rust.adoc b/diaries/rust.adoc new file mode 100644 index 0000000..40e547c --- /dev/null +++ b/diaries/rust.adoc @@ -0,0 +1,3 @@ += Learning Rust + +My progress documented. The diary is in German. \ No newline at end of file diff --git a/diaries/rust/00 - Hello World.adoc b/diaries/rust/00 - Hello World.adoc new file mode 100644 index 0000000..bb0f2ed --- /dev/null +++ b/diaries/rust/00 - Hello World.adoc @@ -0,0 +1,47 @@ +== Hello world + +Hello world ist relativ einfach. `println!` ist ein Makro (eine +spezielle Art Funktion?), die einfach auf stdout printed. + + +~*In[2]:*~ +[source, Rust] +---- +println!("Hello world!"); +---- + + +~*Out[2]:*~ +---- +Hello world! +---- + +== Komplettes Programm + +Rust hat ähnlich wie C eine `main`-Funktion, die zum Start ausgeführt +wird. + +Ein komplettes Programm zum Kompilieren hätte also den folgenden Inhalt: + + +~*In[3]:*~ +[source, Rust] +---- +fn main() { + println!("Hello world!"); +} +---- + +Kompiliert und ausgeführt wird es dann über folgende Befehle: + +[source,bash] +---- +$ rustc main.rs +$ ./main +Hello world! +---- + +== Weitere Details + +* `fn` -> Funktionsdeklaration +* 4 Leerzeichen zum Einrücken, kein Tab +* `;` am Ende der Zeile diff --git a/diaries/rust/01 - Cargo.adoc b/diaries/rust/01 - Cargo.adoc new file mode 100644 index 0000000..d8844a5 --- /dev/null +++ b/diaries/rust/01 - Cargo.adoc @@ -0,0 +1,65 @@ +== Cargo + +Cargo ist Rusts package manager. + +Um ein neues Cargo-Projekt zu erstellen, braucht es das folgende +Command: + +[source,bash] +---- +$ cargo new projektname --bin +---- + +`--bin` sagt, dass wir ein neues Binary erstellen und keine +Bibliothek. + +Es wird auch gleich `main.rs`, ein `.git`-Ordner (inkl. `.gitignore`) +und `Cargo.toml` erstellt. + +== Angelegte Datein + +=== Cargo.toml + +Unangetastet sieht die Datei so aus: + +[source,toml] +---- +[package] +name = "projektname" +version = "0.1.0" +authors = ["Your Name "] + +[dependencies] +---- + +Hier können also Meta-Infos wie Name und Dependencies gespeichert +werden. + +=== main.rs + +Die Main-Datei ist mit ``Hello World'' gefüllt. + +== Commands + +=== cargo build + +[source,bash] +---- +$ cargo build +$ ./target/debug/projektname +---- + +Standardmäßig wird ein Debug-Build erzeugt. `cargo build --release` +erzeugt einen Release-Build. + +=== cargo run + +Macht einen build und führt die Datei dann aus. + +=== cargo check + +Checkt alles einmal durch. + +=== cargo update + +Updatet alle Dependencies. Allerdings nur auf die letzte Subversion der +angegebenen Version. Will man eine neue Version, muss man das manuell +angeben. diff --git a/diaries/rust/02 - Higher-Lower-Spiel.adoc b/diaries/rust/02 - Higher-Lower-Spiel.adoc new file mode 100644 index 0000000..202237f --- /dev/null +++ b/diaries/rust/02 - Higher-Lower-Spiel.adoc @@ -0,0 +1,324 @@ +== Erstes Spiel + +=== Projekt erstellen + +Das Projekt wird wie in Notebook 01 beschrieben erstellt. + +== Einen Input aufnehmen + + +~*In[2]:*~ +[source, Rust] +---- +:dep evcxr_input +// Das ^ ist für Jupyter +// Das v würde man wirklich benutzen +// use std::io; + +println!("Guess the number!"); + +println!("Please input your guess."); + +let mut guess = evcxr_input::get_string("Number? "); +// Das ^ ist für Jupyter +// Das v würde man wirklich benutzen +//let mut guess = String::new(); + +//io::stdin().read_line(&mut guess) +// .expect("Failed to read line"); + +println!("You guessed: {}", guess); +---- + + +~*Out[2]:*~ +---- +Guess the number! +Please input your guess. +Number? 42 +You guessed: 42 +---- + +== Was haben wir gemacht? + +* `use std::io;` bindet die Standard-IO Bibliothek ein +* `let mut guess` legt eine Variable `guess` an +** `mut` bedeutet, dass sie ``mutable'' also veränderbar ist +* `String::new()` erstellt eine neue Instanz der `String`-Klasse +* `io::stdin()` legt ein `Stdin`-Objekt an - ein Handler für die +CLI-Eingabe +** ohne die ``use'' Anweisung oben, müsste es `std::io::stdin()` sein +* `.read_line(&mut guess)` ließt eine Zeile und speichert sie in guess +** `&` erstellt dabei eine Referenz (wie in C) +** Referenzen sind standardmäßig imutable - deshalb `&mut` +** `read_line()` gibt ein `Result`-Objekt zurück, dieser kann `Ok` oder +`Err` enthalten +* `.expect("Fehlermeldung")` entpackt das `Result`-Objekt +** Theoretisch ist das unnötig, sonst gibt es aber eine Warnung +** Sollte ein `Err` im Result sein, wird durch `expect()` eine Exception +auftreten +* `println!("Eingabe: {}", guess)` ist ein formatiertes print + +== Eine random Zahl erstellen + +Für eine random Zahl brauchen wir die erste Dependency. + +Also `Cargo.toml` bearbeiten: + +[source,toml] +---- +[dependencies] +rand = "0.3.14" +---- + +(In Jupyter müssen wir das anders lösen.) + +Dependencies findet man auch auf https://crates.io[crates.io]. + +Die crate `rand` kann jetzt im Code verwendet werden. + + +~*In[3]:*~ +[source, Rust] +---- +:dep rand = "0.3.15" +// Das ^ ist von Jupyter + +extern crate rand; +use rand::Rng; + +let secret_number: u32 = rand::thread_rng().gen_range(1, 101); + +println!("{}", secret_number); +---- + + +~*Out[3]:*~ +---- +37 +---- + +== Höher oder tiefer? + +Vergleichen wir doch einfach mal… + +Ein Fehler? + + +~*In[4]:*~ +[source, Rust] +---- +use std::cmp::Ordering; + +match guess.cmp(&secret_number) { + Ordering::Less => println!("Too small!"), + Ordering::Greater => println!("Too big!"), + Ordering::Equal => println!("You win!"), +} +---- + + +~*Out[4]:*~ +---- + + match guess.cmp(&secret_number) { + + ^^^^^^^^^^^^^^ expected struct `String`, found `u32` + + mismatched types + +---- + +Unser `guess` ist ja ein `String`! Den kann man nicht einfach mit einem +`int` vergleichen (anscheinend). + +Wir müssen unser guess also umwandeln: + + +~*In[5]:*~ +[source, Rust] +---- +let guess: u32 = guess.trim().parse().expect("Please type a number!"); +---- + +`.strip()` entfernt Whitespace von beiden Seiten und `parse()` macht +eine Zahl draus. + +`guess` als Variable ist schon vorhanden? Kein Problem! Rust erlaubt +``Shadowing'', damit man nicht mehrere Variablen unterschiedlicher +Datentypen für den selben Wert anlegen muss. + +Jetzt sollte das Vergleichen auch klappen! + + +~*In[6]:*~ +[source, Rust] +---- +use std::cmp::Ordering; + +match guess.cmp(&secret_number) { + Ordering::Less => println!("Too small!"), + Ordering::Greater => println!("Too big!"), + Ordering::Equal => println!("You win!"), +} +---- + + +~*Out[6]:*~ +---- +Too big! +()---- + +== Nicht nur ein Versuch + +Damit wir mehrmals raten können, brauchen wir eine Schleife. + + +~*In[7]:*~ +[source, Rust] +---- +let secret_number: u32 = rand::thread_rng().gen_range(1, 101); +loop { + let mut guess = evcxr_input::get_string("Number? "); + let guess: u32 = guess.trim().parse().expect("Please type a number!"); + + match guess.cmp(&secret_number) { + Ordering::Less => println!("Too small!"), + Ordering::Greater => println!("Too big!"), + Ordering::Equal => println!("You win!"), + } +} +---- + + +~*Out[7]:*~ +---- +Number? 100 +Too big! +Number? 50 +Too big! +Number? 25 +Too small! +Number? 30 +Too small! +Number? 40 +Too small! +Number? 42 +Too small! +Number? 45 +You win! +Number? 45 +You win! +Number? 100 +Too big! +Number? 45 +You win! +Number? + +thread '' panicked at 'Please type a number!: ParseIntError { kind: Empty }', src/lib.rs:133:43 +stack backtrace: + 0: rust_begin_unwind + at /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/std/src/panicking.rs:517:5 + 1: core::panicking::panic_fmt + at /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/core/src/panicking.rs:100:14 + 2: core::result::unwrap_failed + at /rustc/f1edd0429582dd29cccacaf50fd134b05593bd9c/library/core/src/result.rs:1616:5 + 3: run_user_code_5 + 4: evcxr::runtime::Runtime::run_loop + 5: evcxr::runtime::runtime_hook + 6: evcxr_jupyter::main +note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace. +Segmentation fault. + + + Child process terminated with status: signal: 11 (core dumped) + +---- + +Funktioniert, aber selbst nach dem Erraten passiert nichts und wir +sollen weiter raten. + +Offensichtlich müssen wir die Schleife dann abbrechen. + + +~*In[8]:*~ +[source, Rust] +---- +let secret_number: u32 = rand::thread_rng().gen_range(1, 101); +loop { + let mut guess = evcxr_input::get_string("Number? "); + let guess: u32 = guess.trim().parse().expect("Please type a number!"); + + match guess.cmp(&secret_number) { + Ordering::Less => println!("Too small!"), + Ordering::Greater => println!("Too big!"), + Ordering::Equal => { + println!("You win!"); + break; + }, + } +} +---- + + +~*Out[8]:*~ +---- +Number? 100 +Too big! +Number? 50 +Too big! +Number? 25 +Too small! +Number? 42 +Too big! +Number? 39 +Too big! +Number? 37 +Too big! +Number? 36 +Too big! +Number? 33 +Too big! +Number? 30 +Too big! +Number? 29 +You win! +()---- + +== Error handling + +Derzeit stirbt das Programm einfach mit einem Fehler, wenn man keine +Zahl eingibt. Das können wir auch relativ einfach fixen: + + +~*In[9]:*~ +[source, Rust] +---- +loop { + let mut guess = evcxr_input::get_string("Number? "); + let guess: u32 = match guess.trim().parse() { + Ok(num) => num, + Err(_) => continue, + }; + // Wenn wir hier her kommen, haben wir eine gültige Zahl und beenden einfach. + break; +} +---- + + +~*Out[9]:*~ +---- +Number? a +Number? b +Number? 🦀 +Number? 5 +() +---- + +Statt einem `expect()` haben wir nun eine `match`-Expression. Die Syntax +ist relativ einfach zu verstehen. Man kann auch mehrere `Ok(value)` +nutzen, wobei dann das richtige aufgerufen wird. `Err(_)` nutzt den +Unterstrich, um alle Fehler zu catchen, nicht nur einen speziellen. + +Das `num` nach dem Pfeil ist ein implizites Return. Wenn eine Variable +am Ende eines Blocks steht, wird sie zurückgegeben. + +== Fertig + +Wir haben nun alle Elemente für das ``Higher-Lower-Game''. diff --git a/list.json b/list.json index f3a74e5..fde931e 100644 --- a/list.json +++ b/list.json @@ -14,5 +14,20 @@ "Have fun!" ], "repo": "https://git.c0ntroller.de/c0ntroller/frontpage" + }, + { + "type": "diary", + "name": "rust", + "short": "Me learning Rust (German).", + "desc": [ + "I documented my progress to lern the Rust programming language.", + "The pages are exported from a #{jupyter notebook|https://jupyter.org/}, which I use to follow the Rust book.", + "Everything is in German as these are my own notes." + ], + "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"} + ] } ] \ No newline at end of file