Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Note

Hinweis: Diese Inhalte wurden mit Unterstützung von Künstlicher Intelligenz erstellt und redaktionell überprüft (Transparenzhinweis gemäß Art. 50 EU AI Act).

Kapitel 01b: Der harte Rust-Wochenplan (Dein Lernpfad)

Rust lernt man nicht durch passives Lesen, sondern durch aktives Schreiben, Scheitern am Compiler und diszipliniertes Voranschreiten. Dieser Lernpfad ist als harter 8-Wochen-Trainingsplan konzipiert. Er teilt die 20 Kapitel dieses Buches in wöchentliche Meilensteine auf. Jede Woche definiert exakt, welche Themen du beherrschen musst, welche Übungen zu lösen sind und welche typischen Compilerfehler dir begegnen werden.

Wähle auch hier wieder deine Sichtweise:

  • Für Anfänger (Einfach): Konzentriert sich auf didaktische Meilensteine, Alltagsanalogien und einen sanften, aber disziplinierten Einstieg.
  • Für Profis (Architektur): Richtet sich an Entwickler mit Vorkenntnissen und fokussiert sich auf Entwurfsprinzipien, Typsicherheit und Systemdesign.
  • Hardware-Sicht (CPU/RAM): Analysiert jede Woche, was physisch im Arbeitsspeicher und im Prozessor geschieht.
  • Praxisteil & Übungen: Zeigt dir, wie du deinen wöchentlichen Fortschritt über die Testsuite des Workspace hart überprüfst.

Kapitel 01b: Der 8-Wochen-Trainingsplan (Sicht für Anfänger)

Dieser Plan führt dich in 8 Wochen von den ersten Schritten bis zur Erstellung sicherer und performanter Programme. Wenn du eine Woche erfolgreich abschließen willst, musst du die genannten Kernfragen beantworten und die Übungen fehlerfrei kompilieren können.


Woche 1: Das Fundament (Kapitel 01, 02 & 03)

  • Themen: Workspace-Setup, rustup, Cargo-Befehle, Variablen, Unveränderlichkeit, Shadowing und primitive Datentypen (Integers, Floats, Booleans, Chars, Tupel, Arrays).
  • Die Analogie: Variablen in Rust sind wie beschriftete Aufbewahrungsboxen. Standardmäßig sind sie mit Sekundenkleber verschlossen (unveränderlich). Wenn du den Inhalt austauschen willst, musst du die Box explizit als “wiederöffnbar” markieren (mut).
  • Das musst du wissen:
    1. Was ist der Unterschied zwischen cargo check und cargo build?
    2. Warum stürzt ein Programm ab, wenn ein Integer überläuft (Overflow), und wie verhindert Rust das?
    3. Was ist Shadowing und warum ist es nützlicher als eine Variable veränderbar zu machen?
  • Der Compilerfehler der Woche:
    fn main() {
        let x = 5;
        x = x + 1; // FEHLER!
    }
    Erklärung: Der Compiler meckert, weil x unveränderlich ist. Du musst entweder let mut x = 5; schreiben oder Shadowing nutzen: let x = x + 1; (wodurch eine neue Variable x die alte überschattet).
  • Praxis: Löse die Übungen in exercises/01_variables/.

Woche 2: Die Kletter-Sicherung: Ownership & Strings (Kapitel 04 & 05)

  • Themen: Ownership (Besitzregeln), Borrowing (Ausleihen), Referenzen (& und &mut), Lebensdauern (Lifetimes) Einführung, String vs. &str, UTF-8 Codierung.
  • Die Analogie: Ownership ist wie ein Bibliotheksbuch. Es gibt immer nur einen Eigentümer. Du kannst das Buch an deine Freunde ausleihen (Referenz). Wenn du es veränderbar ausleihst (&mut), darf in dieser Zeit kein anderer das Buch auch nur anschauen, damit niemand verwirrt wird, während Seiten umgeschrieben werden.
  • Das musst du wissen:
    1. Was passiert mit Heap-Daten, wenn ihr Besitzer (Owner) den Gültigkeitsbereich (Scope) verlässt?
    2. Warum darfst du nicht gleichzeitig eine veränderbare und eine unveränderbare Referenz auf dasselbe Datum haben?
    3. Was ist der Unterschied zwischen einem dynamischen String auf dem Heap und einem statischen Textausschnitt &str?
  • Der Compilerfehler der Woche:
    fn main() {
        let s1 = String::from("hallo");
        let s2 = s1; // Ownership wird verschoben (Move)
        println!("{}", s1); // FEHLER! s1 ist ungültig!
    }
    Erklärung: Da String Heap-Speicher besitzt, verschiebt Rust den Besitz an s2. s1 darf danach nicht mehr benutzt werden. Lösung: Nutze s1.clone(), wenn du eine echte Kopie willst, oder übergib eine Referenz: let s2 = &s1;.
  • Praxis: Löse die Übungen in exercises/02_ownership/ und exercises/03_strings/.

Woche 3: Daten strukturieren (Kapitel 06 & 10)

  • Themen: Standard-Collections (Vektoren Vec<T>, Key-Value-Speicher HashMap<K, V>), Structs (Klassische, Tupel- und Unit-like Structs), impl-Blöcke, Methoden und assoziierte Funktionen.
  • Die Analogie: Ein Struct ist wie der Steckbrief eines Rollenspielcharakters. Er fasst verschiedene Werte (Name, Lebenspunkte, Stärke) zusammen. Die Methoden im impl-Block sind die Fähigkeiten, die dieser Charakter ausführen kann.
  • Das musst du wissen:
    1. Wann benutzt man einen Vektor und wann eine HashMap?
    2. Was ist der Unterschied zwischen self, &self und &mut self in einer Methode?
    3. Wie erstellt man ein Struct, das keine Daten, sondern nur Verhalten besitzt (Unit-like Struct)?
  • Der Compilerfehler der Woche:
    struct Spieler { name: String }
    impl Spieler {
        fn gruesse(self) {
            println!("Hallo {}", self.name);
        }
    }
    fn main() {
        let s = Spieler { name: String::from("Thorsten") };
        s.gruesse(); // Ownership von s wird verbraucht!
        s.gruesse(); // FEHLER! s wurde bereits verschoben!
    }
    Erklärung: Da gruesse self (ohne &) entgegennimmt, verbraucht die Methode die Ownership des Structs. Lösung: Ändere die Methodensignatur zu fn gruesse(&self).
  • Praxis: Löse die Übungen in exercises/04_collections/ und exercises/05_structs/.

Woche 4: Funktionen & Fehlerbehandlung (Kapitel 07 & 09)

  • Themen: Funktionssignaturen, Ausdrücke vs. Anweisungen, Closures (Fn, FnMut, FnOnce), Fehlerbehandlung mit Option<T> und Result<T, E>, der ?-Operator, Panic vs. geordneter Abbruch.
  • Die Analogie: Fehlerbehandlung in Rust ist wie eine Paketlieferung. Der Bote wirft das Paket nicht einfach über den Zaun und hofft das Beste (Exception). Er gibt dir einen Karton (Result). Wenn du ihn öffnest, findest du entweder die bestellte Ware (Ok) oder einen Zettel mit der Schadensmeldung (Err). Du bist gezwungen, beides anzuschauen.
  • Das musst du wissen:
    1. Was unterscheidet einen Ausdruck (Expression, ohne Semikolon) von einer Anweisung (Statement, mit Semikolon)?
    2. Wie fängt man Fehler mit Pattern Matching ab, ohne das Programm abstürzen zu lassen?
    3. Warum sollte man unwrap() im produktiven Code strikt vermeiden?
  • Der Compilerfehler der Woche:
    #![allow(unused)]
    fn main() {
    fn hole_zahl(eingabe: &str) -> Result<i32, ParseIntError> {
        let zahl = eingabe.parse::<i32>()?; // Fehler wird weitergeleitet
        zahl // FEHLER!
    }
    }
    Erklärung: Rust erwartet am Ende der Funktion einen Rückgabetyp vom Typ Result. Da zahl ein nacktes i32 ist, schlägt die Kompilierung fehl. Lösung: Verpacke den Rückgabewert in Ok(zahl).
  • Praxis: Löse die Übungen in exercises/06_closures/ und exercises/07_errors/.

Woche 5: Kontrolle & Abstraktion (Kapitel 08 & 12)

  • Themen: Kontrollfluss, Pattern Matching im Detail, match (Exhaustivität), if let Syntax, Enums (Algebraische Datentypen mit Daten), Enums vs. Structs.
  • Die Analogie: Ein Enum ist wie eine Ampel. Sie kann rot, gelb oder grün sein, aber niemals alles gleichzeitig. In Rust kann jede dieser Farben sogar noch zusätzliche Informationen tragen (z.B. Rot zeigt die Sekunden an, bis es grün wird).
  • Das musst du wissen:
    1. Was bedeutet es, dass ein match-Block in Rust “exhaustiv” (vollständig) sein muss?
    2. Wann ist die if let-Schreibweise besser geeignet als ein volles match?
    3. Wie speichert man unterschiedliche Datentypen in den Varianten eines einzigen Enums?
  • Der Compilerfehler der Woche:
    #![allow(unused)]
    fn main() {
    enum Status { Aktiv, Inaktiv }
    fn pruefe(s: Status) {
        match s {
            Status::Aktiv => println!("Läuft!"),
            // FEHLER! Variante Inaktiv wird nicht abgedeckt!
        }
    }
    }
    Erklärung: Der Compiler zwingt dich, alle Varianten abzuhandeln. Du musst entweder Status::Inaktiv hinzufügen oder den Wildcard-Operator _ => {} als Standardfall nutzen.
  • Praxis: Löse die Übungen in exercises/08_control_flow/ und exercises/09_enums/.

Woche 6: Schnittstellen & Generics (Kapitel 11, 13 & 14)

  • Themen: Traits (Schnittstellen), Trait Bounds, impl Trait vs. dyn Trait, Generics in Funktionen, Structs und Enums, Module (mod), Cargo-Crates und Sichtbarkeiten (pub).
  • Die Analogie: Ein Trait ist wie ein Führerschein. Das Auto (Struct) fragt dich nicht, wer du bist. Es will nur wissen: “Hast du den Führerschein der Klasse B?” Wenn ja, darfst du fahren. Jeder Datentyp, der den Trait implementiert, darf an die Funktion übergeben werden.
  • Das musst du wissen:
    1. Was ist der Unterschied zwischen statischem Dispatch (impl Trait) und dynamischem Dispatch (dyn Trait)?
    2. Wie beschränkt man generische Typen so, dass sie nur bestimmte Methoden unterstützen (Trait Bounds)?
    3. Wie strukturierst du ein Projekt mit Modulen über mehrere Dateien hinweg?
  • Der Compilerfehler der Woche:
    #![allow(unused)]
    fn main() {
    trait Reden { fn sprich(&self); }
    fn sag_was<T>(ding: T) {
        ding.sprich(); // FEHLER!
    }
    }
    Erklärung: Der Compiler weiß nicht, ob der Typ T überhaupt sprechen kann. Lösung: Du musst dem Compiler mitteilen, dass T den Trait Reden besitzen muss: fn sag_was<T: Reden>(ding: T).
  • Praxis: Löse die Übungen in exercises/10_traits/ und exercises/11_generics/.

Woche 7: Fortgeschrittene Programmierung (Kapitel 15 & 16)

  • Themen: Iteratoren (Lazy Evaluation, map, filter, collect), Nebenläufigkeit (Threads), Message Passing (Channels), Shared State (Arc<Mutex<T>>), asynchrone Programmierung mit async/await.
  • Die Analogie: Ein Iterator ist wie ein Fließband in einer Fabrik. Die Kisten (Daten) liegen darauf, aber das Fließband bewegt sich erst, wenn am Ende jemand ein Paket abholt (collect). Vorher wird kein einziger Arbeitsschritt ausgeführt.
  • Das musst du wissen:
    1. Warum sind Iteratoren in Rust “lazy” (träge)?
    2. Was ist der Unterschied zwischen Rc<T> (Single-Thread) und Arc<T> (Multi-Thread)?
    3. Wie schützt ein Mutex Daten vor dem gleichzeitigen Zugriff durch mehrere Threads?
  • Der Compilerfehler der Woche:
    use std::thread;
    fn main() {
        let daten = vec![1, 2, 3];
        thread::spawn(|| {
            println!("{:?}", daten); // FEHLER!
        });
    }
    Erklärung: Der Thread könnte länger leben als die Funktion main. Daher erlaubt der Compiler nicht, dass der Thread eine Referenz auf daten ausleiht. Lösung: Nutze das move-Schlüsselwort vor der Closure, um die Ownership komplett in den Thread zu verschieben: thread::spawn(move || { ... }).
  • Praxis: Löse die Übungen in exercises/12_iterators/ und exercises/13_concurrency/.

Woche 8: Professionalisierung (Kapitel 17, 18, 19 & 20)

  • Themen: Metaprogrammierung mit Makros (macro_rules!), Testautomatisierung (#[test]), Dokumentation, Unsafe Rust (rohe Zeiger), FFI (Fremdsprachen-Interaktion), Infrastruktur des Buchs.
  • Die Analogie: Unsafe Rust ist wie das Ausschalten des ESP beim Auto. Du kannst jetzt driften und Dinge tun, die der Fahrassistent sonst verbietet. Aber wenn du gegen die Wand fährst, gibt es keine Knautschzone mehr. Du bist selbst dafür verantwortlich, dass nichts explodiert.
  • Das musst du wissen:
    1. Wann ist es legitim, einen unsafe-Block zu schreiben, und warum sollte man es im normalen Code vermeiden?
    2. Was ist der Unterschied zwischen Unit-Tests und Integrationstests in Rust?
    3. Wie generiert man Dokumentation direkt aus dem Quellcode?
  • Der Compilerfehler der Woche:
    fn main() {
        let mut x = 5;
        let r1 = &mut x as *mut i32; // Roher Zeiger erstellen
        println!("Wert: {}", *r1); // FEHLER!
    }
    Erklärung: Das Dereferenzieren eines rohen Zeigers (*r1) ist eine unsichere Operation, da der Compiler nicht garantieren kann, dass der Zeiger auf gültigen Speicher zeigt. Lösung: Die Dereferenzierung muss in einen unsafe-Block gepackt werden: unsafe { println!("{}", *r1); }.
  • Praxis: Löse die Übungen in exercises/14_macros/ und exercises/15_unsafe/.

Dein Wochen-Meilenstein (Der Selbsttest)

Um zu prüfen, ob du eine Woche erfolgreich abgeschlossen hast, beantworte die Kernfragen schriftlich und stelle sicher, dass alle Tests der Woche mit cargo test grün leuchten.


Kapitel 01b: Der 8-Wochen-Trainingsplan (Sicht für Profis)

Für Entwickler mit fundierten Vorkenntnissen (C++, Java, Go, Python) ist dieser Plan darauf ausgelegt, die mentalen Konzepte umzutrainieren. Sie müssen nicht lernen, wie man programmiert, sondern wie man ohne Garbage Collector und ohne manuelle Speicherfehler hochperformante Systeme entwirft.


Woche 1: Statische Typsicherheit & Cargo-Workflow (Kapitel 01 - 03)

  • Fokus: Typsystem, Typinferenz, Shadowing, Konstanten vs. Variablen.
  • Architektur-Fokus: Verstehen Sie, warum Rust keine impliziten Typkonvertierungen erlaubt (z.B. kein automatischer Cast von i32 zu f64). Nutzen Sie Shadowing zur Transformation von Datentypen innerhalb desselben Scopes, um Namenskonflikte zu vermeiden und Unveränderlichkeit zu wahren.
  • Kernfrage: Warum ist Shadowing speicher- und laufzeiteffizienter als veränderlicher Zustand (mut)?
  • Compilerfehler:
    #![allow(unused)]
    fn main() {
    let x: i32 = 10;
    let y: f64 = x; // Compilerfehler: Typen stimmen nicht überein
    }
    Lösung: Expliziter Cast mit let y = x as f64;. Rust erzwingt absolute Klarheit auf Typebene, um verdeckte Rechenungenauigkeiten zu verhindern.

Woche 2: Die RAII-Revolution: Ownership & Lifetimes (Kapitel 04 & 05)

  • Fokus: Resource Acquisition Is Initialization (RAII), Compile-Time Memory Management, Borrow Checker.
  • Architektur-Fokus: Rust verschiebt das Ressourcenmanagement komplett in den Compiler. Ein Wert hat genau einen Eigentümer. Wird dieser ungültig, wird die Ressource freigegeben. Referenzen werden statisch über Lebensdauern (Lifetimes) analysiert.
  • Kernfrage: Wie verhindert der Borrow Checker zur Kompilierzeit Use-after-free und Double-free?
  • Compilerfehler:
    #![allow(unused)]
    fn main() {
    let r;
    {
        let x = 5;
        r = &x; // Compilerfehler: `x` lebt nicht lange genug
    }
    println!("{}", r);
    }
    Lösung: Die Lebenszeit von x muss mindestens so lang sein wie die von r. Rust verhindert Dangling Pointers statisch.

Woche 3: Datenkapselung ohne Klassenhierarchien (Kapitel 06 & 10)

  • Fokus: Structs, Composition over Inheritance, Datenstrukturen der Standardbibliothek.
  • Architektur-Fokus: Trennen Sie Daten (Structs) strikt von Verhalten (impl-Blöcke). Vermeiden Sie zyklische Objektreferenzen. Entwerfen Sie Datenstrukturen als gerichtete Graphen.
  • Kernfrage: Warum verbietet Rust die Vererbung auf Datenebene und wie wird Polymorphie stattdessen realisiert?
  • Compilerfehler: Versuch, zwei Structs über gegenseitige veränderbare Referenzen zu verknüpfen. Dies führt zu permanenten Konflikten mit dem Borrow Checker. Lösung: Datenflüsse linearisieren oder Indizes in einem Vector nutzen statt roher Pointer.

Woche 4: Fehler als Daten & Funktionale Closures (Kapitel 07 & 09)

  • Fokus: Monadische Fehlerbehandlung, Closures als Typen.
  • Architektur-Fokus: Keine Exceptions. Fehler sind normale Rückgabetypen (Result<T, E>). Der ?-Operator erlaubt eine elegante Fehlerweiterleitung. Closures implementieren automatisch einen der drei Traits: Fn, FnMut oder FnOnce, abhängig davon, wie sie Variablen aus ihrer Umgebung einfangen.
  • Kernfrage: Was unterscheidet die Speicherallokation von Fn (unveränderlicher Borrow) von FnOnce (Konsumierung des Scopes)?
  • Compilerfehler: Nutzung einer Variable nach ihrer Konsumierung durch eine FnOnce-Closure. Lösung: Variable vor der Closure klonen oder die Closure so definieren, dass sie nur Referenzen einfängt.

Woche 5: Algebraische Datentypen (Kapitel 08 & 12)

  • Fokus: Pattern Matching, Enums mit Datenpayload.
  • Architektur-Fokus: Nutzen Sie Enums zur Darstellung komplexer Zustandsmaschinen. Jede Enum-Variante kann unterschiedliche Datenstrukturen aufnehmen. Der Compiler garantiert, dass alle Zustände in match-Blöcken behandelt werden.
  • Kernfrage: Wie optimiert der Compiler Enums mit Option<&T> über die Null-Pointer-Optimierung?
  • Compilerfehler: Fehlende Abdeckung einer Enum-Variante im match-Block. Lösung: Fügen Sie die Variante hinzu oder nutzen Sie den Catch-All-Zweig (_).

Woche 6: API-Design, Generics & Module (Kapitel 11, 13 & 14)

  • Fokus: Traits, Generische Typen, Monomorphisierung.
  • Architektur-Fokus: Definieren Sie wiederverwendbare APIs über Traits. Verwenden Sie statischen Dispatch (impl Trait), um zur Kompilierzeit spezialisierten Code zu erzeugen (Zero-Cost). Nutzen Sie dynamischen Dispatch (dyn Trait) nur bei heterogenen Collections zur Laufzeit.
  • Kernfrage: Wie wirkt sich Monomorphisierung auf die Codegröße (Code Bloat) und die Ausführungsgeschwindigkeit aus?
  • Compilerfehler: Versuch, einen Trait mit assoziierten Typen in einem Vektor vom Typ Vec<Box<dyn MeinTrait>> zu speichern. Lösung: Nutzen Sie stattdessen Generics oder löschen Sie die assoziierten Typen aus dem Trait (Objektsicherheit).

Woche 7: Concurrency & Asynchronität (Kapitel 15 & 16)

  • Fokus: Data-Race-Freiheit, Send/Sync-Traits, Futures, Async/Await.
  • Architektur-Fokus: Rust garantiert die Abwesenheit von Data Races zur Kompilierzeit. Typen, die sicher an andere Threads übergeben werden können, implementieren Send. Typen, deren Referenzen sicher geteilt werden können, implementieren Sync.
  • Kernfrage: Wie arbeiten Arc und Mutex zusammen, um threadsicheren, veränderlichen Zustand zu ermöglichen?
  • Compilerfehler: rc::Rc<T> cannot be sent between threads safely (Fehlender Send-Trait bei Rc). Lösung: Ersetzen Sie Rc im Multi-Thread-Kontext durch Arc.

Woche 8: Systemnahe Programmierung & Metaprogrammierung (Kapitel 17 - 20)

  • Fokus: Makros, Unsafe Rust, FFI, Testabdeckung.
  • Architektur-Fokus: Verwenden Sie unsafe, um mit C-Bibliotheken zu interagieren oder hochperformante, hardwarenahe Datenstrukturen zu schreiben. Kapseln Sie unsicheren Code immer hinter einer sicheren, idiomatischen API.
  • Kernfrage: Warum führt unsicherer Code in einem unsafe-Block nicht automatisch dazu, dass Rusts Typsicherheitsgarantien im restlichen Programm verfallen?
  • Compilerfehler: Versuch, rohe Zeiger außerhalb eines unsafe-Blocks zu dereferenzieren. Lösung: Packen Sie den Zugriff in ein unsafe { ... } Konstrukt.

Kapitel 01b: Der 8-Wochen-Trainingsplan (Hardware- & Systemsicht)

Dieser Plan analysiert die wöchentlichen Konzepte aus der Perspektive der CPU-Architektur, des Speicherlayouts (Stack & Heap) und des vom Compiler generierten Maschinencodes.


Woche 1: Register, Stack-Belegung & Alignments (Kapitel 01 - 03)

  • Hardware-Prozess: Lokale Variablen werden auf dem Stack angeordnet. Der Compiler berechnet das exakte Speicher-Alignment für jeden primitiven Typ (z.B. müssen 32-Bit Integers auf Adressen allokiert werden, die durch 4 teilbar sind).
  • Was im RAM passiert: Shadowing belegt physisch keinen neuen RAM-Speicher, wenn die Lebensdauer der überschatteten Variable endet. Der Compiler optimiert die Registerbelegung (RAX, RCX etc.) so, dass unnötige Stack-Schreibzugriffe minimiert werden.
  • Compilerfehler (Hardware-Perspektive): Umgang mit Integer-Overflows. Im Debug-Modus fügt Rust Checks ein, die bei einem Overflow ein Signal an die CPU senden, um das Programm kontrolliert abzubrechen (panic). Im Release-Modus (Opt-Level 3) wird standardmäßig das hardwareseitige Wrap-Around-Verhalten der CPU genutzt.

Woche 2: Speicherfreigabe ohne Garbage Collector (Kapitel 04 & 05)

  • Hardware-Prozess: Beim Verlassen eines Scopes fügt der Compiler den Destruktor-Aufruf (drop) direkt in den Assemblercode ein. Heap-Speicher wird über einen System-Allocator (z.B. jemalloc oder den Standard-OS-Allocator) freigegeben.
  • Was im RAM passiert: Ein String belegt auf dem Stack exakt 24 Bytes (64-Bit-System): 8 Bytes Pointer auf den Heap-Speicher, 8 Bytes Kapazität und 8 Bytes Länge. Ein Move kopiert lediglich diese 24 Bytes auf dem Stack – die Heap-Daten werden nicht angefasst.
  • Compilerfehler (Hardware-Perspektive): Verhinderung von Dangling Pointers. Der Borrow Checker verhindert statisch, dass ein Zeiger auf dem Stack auf eine Speicheradresse verweist, die bereits freigegeben wurde.

Woche 3: Cache-Lokalität & Struct-Layouts (Kapitel 06 & 10)

  • Hardware-Prozess: Vektoren (Vec<T>) liegen als zusammenhängendes Array im Heap. Dies sorgt für eine exzellente Cache-Lokalität (L1/L2-Caches), da die CPU bei einem Zugriff nachfolgende Daten bereits in einer Cache-Line (typischerweise 64 Bytes) vorlädt.
  • Was im RAM passiert: Rust ordnet die Felder eines Structs im Speicher so an, dass Padding (Leerplatz zur Einhaltung des Alignments) minimiert wird. Der Compiler darf die Reihenfolge der Felder im RAM verändern (im Gegensatz zu C, wo die Deklarationsreihenfolge strikt eingehalten werden muss), es sei denn, man erzwingt dies über #[repr(C)].
  • Compilerfehler (Hardware-Perspektive): Die Übergabe von self per Wert an eine Methode kopiert die Strukturdaten auf dem Stack. Große Strukturen sollten daher per Referenz (&self) übergeben werden, um CPU-Kopierzyklen zu vermeiden.

Woche 4: Stack Frames & Closure-Inlining (Kapitel 07 & 09)

  • Hardware-Prozess: Funktionsaufrufe erzeugen Stack Frames. Rust optimiert diese durch Inlining: Kurze Funktionen werden direkt an der Aufrufstelle eingesetzt, wodurch der Call-Overhead (Push des Instruction Pointers auf den Stack, Sprung, Pop) komplett entfällt.
  • Was im RAM passiert: Closures, die keine Variablen einfangen, werden als normale Funktionszeiger repräsentiert. Closures, die Variablen einfangen, werden vom Compiler in anonyme Structs übersetzt, die die eingefangenen Variablen als Felder halten (entweder als Referenzen oder als Werte).
  • Compilerfehler (Hardware-Perspektive): Vermeidung von Stack Overflows durch unendliche Rekursion. Der Compiler warnt vor rekursiven Aufrufen, die den Stack-Speicher (standardmäßig 2 MB für Threads unter Linux) aufbrauchen würden.

Woche 5: Bitmuster von Enums & Branch Prediction (Kapitel 08 & 12)

  • Hardware-Prozess: Enums werden im Speicher als Tagged Unions dargestellt. Sie bestehen aus einem Diskriminator-Byte (Tag, welcher anzeigt, welche Variante aktiv ist) und dem Payload der größten Variante.
  • Was im RAM passiert: Durch die Null-Pointer-Optimierung belegt Option<&T> exakt so viel Platz wie ein roher Zeiger (8 Bytes). Da ein Referenzzeiger niemals null sein darf, nutzt der Compiler das Bitmuster 0x0 zur Darstellung der Variante None.
  • Compilerfehler (Hardware-Perspektive): Große Enums in match-Blöcken können zu vielen Sprungbefehlen führen. Der Compiler versucht, diese in hocheffiziente Sprungtabellen (Jump Tables) im Maschinencode zu übersetzen, um die Branch Prediction der CPU optimal zu nutzen.

Woche 6: Monomorphisierung & vtables (Kapitel 11, 13 & 14)

  • Hardware-Prozess: Generics in Rust werden durch Monomorphisierung aufgelöst. Für jede genutzte Typkombination generiert der Compiler eine eigene Kopie der Funktion im Maschinencode.
  • Was im RAM passiert:
    • Statischer Dispatch (impl Trait): Direkter Funktionsaufruf im Maschinencode. Kein Laufzeit-Overhead, aber größeres Binärfile (Instruction Cache Belastung).
    • Dynamischer Dispatch (dyn Trait): Der Aufruf erfolgt über einen Fat Pointer. Dieser enthält den Datenzeiger und einen Zeiger auf die vtable (Virtual Method Table). Das Laden der Funktionsadresse aus der vtable kostet eine Speicherindirektion.
  • Compilerfehler (Hardware-Perspektive): Monomorphisierungs-Fehler entstehen, wenn generische Typen die Trait Bounds nicht erfüllen. Dies verhindert, dass der Compiler den Maschinencode für diesen spezifischen Typ korrekt generieren kann.

Woche 7: Thread Scheduling & Cache-Kohärenz (Kapitel 15 & 16)

  • Hardware-Prozess: std::thread::spawn erzeugt einen echten OS-Thread, der vom Scheduler des Betriebssystems auf den CPU-Kernen verteilt wird.
  • Was im RAM passiert: Beim Zugriff auf geteilte Daten über Mutex müssen die CPU-Kerne ihre L1-Caches synchronisieren (Cache-Kohärenz-Protokolle wie MESI). Ein Mutex nutzt atomare CPU-Befehle (z.B. Compare-And-Swap / CAS), um den Lock-Zustand threadsicher abzufragen und zu verändern, ohne in den Kernel-Modus wechseln zu müssen (Futex unter Linux).
  • Compilerfehler (Hardware-Perspektive): Data Races entstehen, wenn zwei CPU-Kerne ohne Synchronisation auf dieselbe Speicheradresse zugreifen und mindestens einer schreibt. Rust verhindert dies, indem es den Zugriff nur erlaubt, wenn die Typen den Send- und Sync-Trait implementieren.

Woche 8: Rohe Zeiger & LLVM-Optimierungen (Kapitel 17 - 20)

  • Hardware-Prozess: Rohe Zeiger (*const T, *mut T) sind einfache 64-Bit-Speicheradressen ohne jegliche Sicherheitsprüfung durch die CPU oder den Compiler.
  • Was im RAM passiert: Im unsafe-Block umgehen Sie den Borrow Checker. Sie können direkt auf Register zugreifen, Memory-Mapped I/O (MMIO) durchführen oder Speicher direkt allokieren. Der LLVM-Optimizer analysiert Ihren Code und wendet aggressive Optimierungen wie Loop Unrolling, Vectorization (SIMD) und Dead Code Elimination an.
  • Compilerfehler (Hardware-Perspektive): Ein Absturz in Unsafe Rust (z.B. ein Segmentierungsfehler / Segmentation Fault) wird durch einen ungültigen Speicherzugriff auf CPU-Ebene ausgelöst (z.B. Zugriff auf geschützten Kernel-Speicher). Der Compiler kann dies im Vorfeld nicht abfangen.

Praxisteil & Übungen: Das wöchentliche Kontrollsystem

Um den harten Wochenplan erfolgreich zu absolvieren, musst du deine Arbeitsumgebung (den Workspace) als unerbittliches Kontrollorgan nutzen. In Rust gilt: Dein Code ist erst fertig, wenn der Compiler schweigt, Clippy zufrieden ist und alle Tests grün sind.


1. Das wöchentliche Prüfsystem: Die drei Stufen der Qualitätskontrolle

Führe am Ende jeder Woche in deinem Terminal folgende Befehle im entsprechenden Übungsverzeichnis aus:

  1. Stufe 1: Syntax & Typen prüfen

    cargo check
    

    Ziel: Keine roten Fehlermeldungen mehr. Der Compiler muss den Code logisch verstehen.

  2. Stufe 2: Logik validieren (Die Unit-Tests)

    cargo test
    

    Ziel: Alle Tests müssen mit ok bewertet werden. Wenn ein Test fehlschlägt, ist deine Implementierung logisch fehlerhaft, selbst wenn sie kompiliert.

  3. Stufe 3: Stil & Best Practices prüfen

    cargo clippy
    

    Ziel: Keine Warnungen. Clippy ist der eingebaute Linter von Rust, der dich auf ineffizienten Code, unnötige Zuweisungen oder unidiomatischen Stil hinweist.


2. Der Woche-2-Selbsttest: Ownership-Validierung

Bist du bereit für Woche 2? Kopiere das folgende Programm in deine src/main.rs. Es enthält absichtlich einen Fehler, den du korrigieren musst. Es dient als Einstiegstest, um zu prüfen, ob du das Konzept von Ausleihen (Borrowing) verstanden hast.

1: fn berechne_laenge(s: String) -> usize {
2:     s.len()
3: } // Hier wird 's' zerstört!
4: 
5: fn main() {
6:     let text = String::from("Rust-Trainingsplan");
7:     
8:     // Wir übergeben 'text' per Wert (Ownership verschiebt sich)
9:     let laenge = berechne_laenge(text);
10:    
11:    // FEHLER: 'text' wurde in Zeile 9 verschoben und ist hier ungültig!
12:    println!("Der Text '{}' hat die Länge {}.", text, laenge);
13: }
14: 
15: #[cfg(test)]
16: mod tests {
17:     use super::*;
18: 
19:     #[test]
20:     fn test_laenge() {
21:         let test_str = String::from("Test");
22:         // Der Test soll prüfen, ob wir die Länge ermitteln können,
23:         // ohne die Variable unbrauchbar zu machen.
24:         assert_eq!(test_str.len(), 4);
25:     }
26: }

Deine Aufgabe zur Freischaltung von Woche 3:

  1. Führe cargo check aus und analysiere die Fehlermeldung.
  2. Ändere die Signatur von berechne_laenge in Zeile 1 so ab, dass sie eine Referenz (&String oder besser &str) akzeptiert.
  3. Passe den Aufruf in Zeile 9 so an, dass eine Referenz übergeben wird (&text).
  4. Führe cargo test aus. Sobald alle Tests grün sind, hast du Woche 2 gemeistert und darfst mit Woche 3 fortfahren!

Note

Hinweis: Diese Inhalte wurden mit Unterstützung von Künstlicher Intelligenz erstellt und redaktionell überprüft (Transparenzhinweis gemäß Art. 50 EU AI Act).