Files
it-know-how/Rust.md
T
2026-04-02 22:31:09 +02:00

746 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
```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::<i32>().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<String>, 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<T>`
```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<K,V>`
```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<T>`** (statt `None`) und **`Result<T, E>`** (statt Exceptions).
### Option
```rust
fn first(v: &[i32]) -> Option<i32> {
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<String, std::io::Error> {
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<i32> = 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<T: Clone>(v: &[T]) -> Option<T> {
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<T: std::fmt::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<T>`) 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<T>`: heap allocation, Ownership bleibt eindeutig
- `Rc<T>`: shared ownership **single-thread**
- `Arc<T>`: shared ownership **thread-safe**
- `RefCell<T>`: “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<T>`
- Dict ↔ `HashMap<K,V>`
- `None``Option<T>`
- Exceptions ↔ `Result<T,E>` + `?`
- 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<String> = 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::<usize>() {
if idx >= 1 && idx <= todos.len() {
todos.remove(idx - 1);
} else {
println!("index out of range");
}
} else {
println!("usage: done <number>");
}
} else {
println!("commands: add <text> | list | done <n> | 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 17 (Syntax, Structs/Enums)
2. Ownership/Borrowing (Kapitel 810) 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?
```