From 664817929496c7c94ba326410b36c1809f4fe9ed Mon Sep 17 00:00:00 2001 From: Mathias Scheider Date: Thu, 2 Apr 2026 22:31:09 +0200 Subject: [PATCH] Rust.md --- Rust.md | 746 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 746 insertions(+) create mode 100644 Rust.md diff --git a/Rust.md b/Rust.md new file mode 100644 index 0000000..ec6e110 --- /dev/null +++ b/Rust.md @@ -0,0 +1,746 @@ +```md +--- +title: "Rust Tutorial (für Python-Erfahrene)" +tags: [rust, tutorial, programming] +created: 2026-03-27 +--- + +# Rust Tutorial (für Python-Erfahrene) + +Dieses Tutorial ist als **hands-on Lernpfad** gedacht: viele kleine Programme, die du direkt ausführen und variieren kannst. Du bekommst dabei die wichtigsten Rust-Konzepte (Ownership/Borrowing, Lifetimes-Grundlagen, Traits, Enums/Pattern Matching, Fehlerbehandlung, Collections, Generics, Module/Crates, Testing, Async-Basics). + +--- + +## 0) Setup: Was brauche ich, wie führe ich Rust aus? + +### Installation (Compiler + Tooling) +Rust wird normalerweise über **rustup** installiert (inkl. `rustc`, `cargo`, Standardbibliothek). + +- **Windows/macOS/Linux:** + - https://rustup.rs öffnen und Anweisungen folgen +- Danach prüfen: +```bash +rustc --version +cargo --version +rustup --version +``` + +### Editor (empfohlen) +- **VS Code** + Extension: **rust-analyzer** +- Alternativ: IntelliJ Rust, Neovim + rust-analyzer, etc. + +### Neues Projekt erstellen und ausführen +Rust nutzt **Cargo** als Build-Tool + Paketmanager. + +```bash +cargo new hello_rust +cd hello_rust +cargo run +``` + +Wichtige Cargo-Kommandos: +```bash +cargo build # kompiliert (debug) +cargo build --release +cargo run # build + run +cargo test # tests +cargo fmt # formatieren (rustfmt) +cargo clippy # lints (clippy) +cargo doc --open # docs generieren + öffnen +``` + +Projektstruktur: +- `src/main.rs` (Binary) +- `Cargo.toml` (Dependencies, Name, Version, etc.) + +--- + +## 1) Hello World + Grundsyntax + +`src/main.rs`: +```rust +fn main() { + println!("Hello, Rust!"); +} +``` + +**Wichtig:** Makro-Aufruf erkennst du am `!` (`println!`, `vec!`, `format!`, …). + +--- + +## 2) Variablen, Mutabilität, Typen + +Rust ist **statisch typisiert**, aber oft mit **Typinferenz**. + +```rust +fn main() { + let x = 5; // immutable + // x = 6; // Fehler + + let mut y = 5; // mutable + y = 6; + + let z: i32 = 42; // expliziter Typ + let pi: f64 = 3.1415; + + println!("{x} {y} {z} {pi}"); +} +``` + +### Shadowing (anders als mut) +```rust +fn main() { + let s = "42"; + let s = s.parse::().unwrap(); // shadowing: neuer s mit anderem Typ + println!("{s}"); +} +``` + +--- + +## 3) Strings: `&str` vs `String` (Python-Vergleich) + +- `&str`: **String Slice**, meist “geliehener” Text (z.B. Stringliteral) +- `String`: **besitzender**, heap-allocierter String (änderbar) + +```rust +fn main() { + let a: &str = "hi"; // slice + let mut b: String = String::from("hi"); // owned + b.push_str(" there"); + + println!("{a} / {b}"); +} +``` + +Sehr häufig konvertieren: +```rust +let s = "abc".to_string(); +let t = String::from("abc"); +let u: &str = &t; // String -> &str +``` + +--- + +## 4) Funktionen + +```rust +fn add(a: i32, b: i32) -> i32 { + a + b // kein Semikolon => Ausdruck +} + +fn main() { + let r = add(2, 3); + println!("{r}"); +} +``` + +Mehr Rückgabewerte via Tupel: +```rust +fn div_mod(a: i32, b: i32) -> (i32, i32) { + (a / b, a % b) +} +``` + +--- + +## 5) Kontrollfluss: `if`, `loop`, `while`, `for` + +```rust +fn main() { + let n = 7; + + if n % 2 == 0 { + println!("even"); + } else { + println!("odd"); + } + + for i in 0..3 { + println!("i={i}"); + } + + let items = vec!["a", "b", "c"]; + for (idx, item) in items.iter().enumerate() { + println!("{idx}: {item}"); + } +} +``` + +`loop` kann Werte zurückgeben: +```rust +fn main() { + let mut i = 0; + let result = loop { + i += 1; + if i == 3 { + break i * 10; // liefert 30 + } + }; + println!("{result}"); +} +``` + +--- + +## 6) Structs, Methoden, `impl` + +```rust +#[derive(Debug, Clone)] +struct User { + name: String, + age: u32, +} + +impl User { + fn new(name: impl Into, age: u32) -> Self { + Self { name: name.into(), age } + } + + fn birthday(&mut self) { + self.age += 1; + } +} + +fn main() { + let mut u = User::new("Alice", 30); + u.birthday(); + println!("{u:?}"); +} +``` + +--- + +## 7) Enums + Pattern Matching (sehr wichtig) + +```rust +#[derive(Debug)] +enum Message { + Quit, + Write(String), + Move { x: i32, y: i32 }, +} + +fn handle(m: Message) { + match m { + Message::Quit => println!("bye"), + Message::Write(text) => println!("text={text}"), + Message::Move { x, y } => println!("move to {x},{y}"), + } +} + +fn main() { + handle(Message::Write("hello".into())); + handle(Message::Move { x: 1, y: 2 }); +} +``` + +`match` ist exhaustiv: alle Fälle müssen behandelt werden (oder `_`). + +--- + +## 8) Ownership, Borrowing, References (Kern von Rust) + +Rust verhindert Data Races und Use-After-Free durch Regeln: + +### Ownership-Regeln (vereinfacht) +1. Jeder Wert hat **genau einen Owner**. +2. Wenn der Owner aus dem Scope läuft, wird der Wert **gedroppt**. +3. **Move**: Zuweisung/Übergabe kann Ownership übertragen. + +Beispiel: Move vs Copy +```rust +fn main() { + let a = String::from("hello"); + let b = a; + // println!("{a}"); // Fehler: a wurde gemoved + + let x = 5; // i32 ist Copy + let y = x; + println!("{x} {y}"); +} +``` + +### Borrowing: `&T` (immutable) und `&mut T` (mutable) +```rust +fn len(s: &String) -> usize { + s.len() +} + +fn add_exclamation(s: &mut String) { + s.push('!'); +} + +fn main() { + let mut s = String::from("hi"); + println!("{}", len(&s)); + + add_exclamation(&mut s); + println!("{s}"); +} +``` + +Borrowing-Regeln: +- beliebig viele `&T` **oder** +- genau ein `&mut T` +- aber nicht beides gleichzeitig (im gleichen Gültigkeitsbereich) + +Typischer Fehler (und Fix durch Scopes): +```rust +fn main() { + let mut s = String::from("abc"); + + let r1 = &s; + let r2 = &s; + println!("{r1} {r2}"); + + let r3 = &mut s; // ok, weil r1/r2 danach nicht mehr benutzt werden + r3.push('d'); + println!("{r3}"); +} +``` + +--- + +## 9) Slices: Teilansichten von Daten + +```rust +fn main() { + let a = [10, 20, 30, 40]; + let mid: &[i32] = &a[1..3]; // 20,30 + println!("{mid:?}"); + + let s = String::from("hello world"); + let w: &str = &s[0..5]; + println!("{w}"); +} +``` + +Achtung bei UTF-8: String-Slicing nur an gültigen Byte-Grenzen von Codepoints. + +--- + +## 10) Collections: `Vec`, `HashMap`, `HashSet` + +### `Vec` +```rust +fn main() { + let mut v = vec![1, 2, 3]; + v.push(4); + + for x in &v { + println!("{x}"); + } + + if let Some(last) = v.pop() { + println!("popped {last}"); + } +} +``` + +### `HashMap` +```rust +use std::collections::HashMap; + +fn main() { + let mut m = HashMap::new(); + m.insert("alice", 10); + m.insert("bob", 7); + + *m.entry("alice").or_insert(0) += 1; + + if let Some(score) = m.get("alice") { + println!("alice={score}"); + } + + for (k, v) in &m { + println!("{k} => {v}"); + } +} +``` + +--- + +## 11) Option und Result: Fehlerbehandlung ohne Exceptions + +Rust nutzt **`Option`** (statt `None`) und **`Result`** (statt Exceptions). + +### Option +```rust +fn first(v: &[i32]) -> Option { + v.first().copied() +} + +fn main() { + let v = vec![1, 2, 3]; + match first(&v) { + Some(x) => println!("first={x}"), + None => println!("empty"), + } +} +``` + +### Result + `?` Operator +```rust +use std::fs; + +fn read_file(path: &str) -> Result { + let text = fs::read_to_string(path)?; + Ok(text) +} + +fn main() { + match read_file("Cargo.toml") { + Ok(t) => println!("len={}", t.len()), + Err(e) => eprintln!("error: {e}"), + } +} +``` + +**`?`**: Wenn `Err`, returnt die Funktion früh; wenn `Ok`, entpackt. + +--- + +## 12) Iterators (sehr “pythonic”, aber typisiert) + +```rust +fn main() { + let nums = vec![1, 2, 3, 4, 5]; + + let squares: Vec = nums.iter() + .map(|x| x * x) + .collect(); + + let even_sum: i32 = nums.iter() + .filter(|x| *x % 2 == 0) + .sum(); + + println!("{squares:?} even_sum={even_sum}"); +} +``` + +Ownership dabei: +- `iter()` gibt `&T` +- `into_iter()` konsumiert und gibt `T` (Owned) +- `iter_mut()` gibt `&mut T` + +--- + +## 13) Generics + Traits (Rusts “Interfaces”) + +### Generics +```rust +fn first(v: &[T]) -> Option { + v.first().cloned() +} +``` + +### Trait definieren und implementieren +```rust +trait Greeter { + fn greet(&self) -> String; +} + +struct Person { name: String } + +impl Greeter for Person { + fn greet(&self) -> String { + format!("Hi, {}!", self.name) + } +} + +fn say_hello(g: &impl Greeter) { + println!("{}", g.greet()); +} + +fn main() { + let p = Person { name: "Alice".into() }; + say_hello(&p); +} +``` + +Trait Bounds: +```rust +fn print_debug(x: T) { + println!("{x:?}"); +} +``` + +--- + +## 14) Module, Crates, Sichtbarkeit (`pub`) + +### In einer Datei +```rust +mod math { + pub fn add(a: i32, b: i32) -> i32 { + a + b + } + + fn secret() {} // privat +} + +fn main() { + println!("{}", math::add(1, 2)); +} +``` + +### Mehrere Dateien (typisches Layout) +- `src/main.rs` +- `src/math.rs` + +`src/main.rs`: +```rust +mod math; + +fn main() { + println!("{}", math::add(1, 2)); +} +``` + +`src/math.rs`: +```rust +pub fn add(a: i32, b: i32) -> i32 { + a + b +} +``` + +--- + +## 15) Lifetimes (Grundidee, praxisnah) + +Lifetimes sagen dem Compiler: “Wie lange sind Referenzen gültig?” + +Beispiel: Funktion gibt eine Referenz zurück: +```rust +fn longer<'a>(a: &'a str, b: &'a str) -> &'a str { + if a.len() >= b.len() { a } else { b } +} + +fn main() { + let x = "short"; + let y = "a bit longer"; + println!("{}", longer(x, y)); +} +``` + +Als Anfänger-Regel: +- Wenn du **Owned** (`String`, `Vec`) zurückgeben kannst, ist das oft einfacher. +- Lifetimes brauchst du vor allem bei APIs, die Referenzen zurückgeben. + +--- + +## 16) Smart Pointers: `Box`, `Rc`, `Arc`, `RefCell` (Überblick) + +- `Box`: heap allocation, Ownership bleibt eindeutig +- `Rc`: shared ownership **single-thread** +- `Arc`: shared ownership **thread-safe** +- `RefCell`: “interior mutability” (Borrow-Regeln zur Laufzeit) + +Mini-Beispiel `Rc`: +```rust +use std::rc::Rc; + +fn main() { + let a = Rc::new(String::from("hello")); + let b = Rc::clone(&a); + + println!("a={}, b={}", a, b); + println!("strong_count={}", Rc::strong_count(&a)); +} +``` + +--- + +## 17) Concurrency (Basics): Threads + Channels + +```rust +use std::thread; +use std::sync::mpsc; + +fn main() { + let (tx, rx) = mpsc::channel(); + + let handle = thread::spawn(move || { + tx.send("hello from thread").unwrap(); + }); + + let msg = rx.recv().unwrap(); + println!("got: {msg}"); + + handle.join().unwrap(); +} +``` + +--- + +## 18) Async (Basics): `async/await` mit Tokio + +Async braucht i.d.R. eine Runtime (z.B. Tokio). + +`Cargo.toml`: +```toml +[dependencies] +tokio = { version = "1", features = ["full"] } +``` + +`src/main.rs`: +```rust +use tokio::time::{sleep, Duration}; + +#[tokio::main] +async fn main() { + let h = tokio::spawn(async { + sleep(Duration::from_millis(100)).await; + 42 + }); + + let result = h.await.unwrap(); + println!("result={result}"); +} +``` + +--- + +## 19) Testing + +```rust +fn add(a: i32, b: i32) -> i32 { a + b } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn adds() { + assert_eq!(add(2, 3), 5); + } +} +``` + +Ausführen: +```bash +cargo test +``` + +--- + +## 20) “Rust für Python-Dev”: mentale Übersetzungen + +- Python-Liste ↔ `Vec` +- Dict ↔ `HashMap` +- `None` ↔ `Option` +- Exceptions ↔ `Result` + `?` +- Duck typing ↔ Traits + Generics +- Mutable Default überall ↔ Rust: **immutable by default** +- Garbage Collector ↔ Ownership (Drop am Scope-Ende) + +**Idiom:** Daten lieber so modellieren, dass `match` alle Fälle abdeckt (Enums sind stark). + +--- + +## 21) Mini-Projekt 1: CLI “todo” (ohne externe Crates) + +Ziel: einfache Aufgabenliste im Speicher (kein Persistenz). + +```rust +use std::io::{self, Write}; + +fn main() { + let mut todos: Vec = Vec::new(); + + loop { + print!("todo> "); + io::stdout().flush().unwrap(); + + let mut line = String::new(); + if io::stdin().read_line(&mut line).is_err() { + println!("input error"); + continue; + } + let line = line.trim(); + + if line == "quit" { + break; + } else if line == "list" { + for (i, t) in todos.iter().enumerate() { + println!("{}: {}", i + 1, t); + } + } else if let Some(rest) = line.strip_prefix("add ") { + todos.push(rest.to_string()); + } else if let Some(rest) = line.strip_prefix("done ") { + if let Ok(idx) = rest.parse::() { + if idx >= 1 && idx <= todos.len() { + todos.remove(idx - 1); + } else { + println!("index out of range"); + } + } else { + println!("usage: done "); + } + } else { + println!("commands: add | list | done | quit"); + } + } +} +``` + +--- + +## 22) Mini-Projekt 2: JSON holen (Reqwest) + +`Cargo.toml`: +```toml +[dependencies] +tokio = { version = "1", features = ["full"] } +reqwest = { version = "0.12", features = ["json"] } +serde = { version = "1", features = ["derive"] } +``` + +`src/main.rs`: +```rust +use serde::Deserialize; + +#[derive(Debug, Deserialize)] +struct HttpBin { + url: String, + origin: String, +} + +#[tokio::main] +async fn main() -> Result<(), reqwest::Error> { + let data: HttpBin = reqwest::get("https://httpbin.org/get") + .await? + .json() + .await?; + + println!("{data:#?}"); + Ok(()) +} +``` + +--- + +## 23) Nächste Schritte / gute Referenzen + +- Offizielles Buch: https://doc.rust-lang.org/book/ +- Rust by Example: https://doc.rust-lang.org/rust-by-example/ +- Standard Library Docs: https://doc.rust-lang.org/std/ + +--- + +## Vorschlag für deinen Lernpfad (kurz) +1. Kapitel 1–7 (Syntax, Structs/Enums) +2. Ownership/Borrowing (Kapitel 8–10) – viel üben +3. Option/Result + Iterators +4. Traits/Generics + Module +5. Tests + kleines Projekt +6. Dann Async/Concurrency nach Bedarf + +--- + +## Offene Fragen (damit ich es besser anpassen kann) +- Willst du Rust eher für **CLI**, **Backend**, **Embedded**, **WebAssembly** oder **Data/Perf** nutzen? +- Betriebssystem/Editor? +- Soll ich daraus **mehrere Obsidian-Notizen** machen (z.B. “Ownership”, “Result/Option”, “Traits”, …) mit internen Links? +``` \ No newline at end of file