```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? ```