Zum Inhalt springen
Rust Anfรคnger 30 min

Structs, Enums & match

Eigene Typen in Rust: Structs fuer zusammengesetzte Daten, Enums fuer Alternativen und match fuer exhaustives Pattern Matching.

Aktualisiert:
Inhaltsverzeichnis

Structs, Enums & match

Damit kommst du zu den Werkzeugen, mit denen du in Rust eigene Datentypen modellierst. Dieses Kapitel ist das Sprungbrett zu โ€œechtenโ€ Rust-Programmen.

Structs

Ein struct gruppiert Daten unter einem Namen:

struct Person {
    name: String,
    alter: u32,
    aktiv: bool,
}

fn main() {
    let anna = Person {
        name: String::from("Anna"),
        alter: 28,
        aktiv: true,
    };

    println!("{} ({})", anna.name, anna.alter);
}

Mutable Structs

Die ganze Instanz wird mutable, nicht einzelne Felder:

let mut anna = Person {
    name: String::from("Anna"),
    alter: 28,
    aktiv: true,
};
anna.alter = 29;

Tuple-Structs

Wenn Feldnamen ueberfluessig waeren:

struct Point(f64, f64);
struct Color(u8, u8, u8);

let origin = Point(0.0, 0.0);
let rot = Color(255, 0, 0);

println!("{}", origin.0);

Methoden mit impl

Du kannst einem Struct Methoden geben:

struct Rechteck {
    breite: f64,
    hoehe: f64,
}

impl Rechteck {
    // Konstruktor - einfach eine Funktion die Self zurueckgibt
    fn neu(breite: f64, hoehe: f64) -> Self {
        Self { breite, hoehe }
    }

    // Methode mit &self (liest nur)
    fn flaeche(&self) -> f64 {
        self.breite * self.hoehe
    }

    // Methode mit &mut self (schreibt)
    fn skalieren(&mut self, faktor: f64) {
        self.breite *= faktor;
        self.hoehe *= faktor;
    }
}

fn main() {
    let mut r = Rechteck::neu(3.0, 4.0);
    println!("Flaeche: {}", r.flaeche()); // 12
    r.skalieren(2.0);
    println!("Flaeche: {}", r.flaeche()); // 48
}

Merke dir die drei Varianten:

  • &self = liest
  • &mut self = schreibt
  • self = nimmt Ownership (seltener)

Enums

Ein enum beschreibt eine von mehreren Alternativen:

enum Ampel {
    Rot,
    Gelb,
    Gruen,
}

fn farbe_beschreiben(a: Ampel) {
    match a {
        Ampel::Rot    => println!("Stoppen"),
        Ampel::Gelb   => println!("Aufpassen"),
        Ampel::Gruen  => println!("Fahren"),
    }
}

Enums mit Daten

Das ist die wahre Staerke: Jede Variante kann eigene Daten haben.

enum Event {
    Klick { x: f64, y: f64 },
    Tastendruck(char),
    Schliessen,
}

fn verarbeite(e: Event) {
    match e {
        Event::Klick { x, y }   => println!("Klick bei {}, {}", x, y),
        Event::Tastendruck(c)   => println!("Taste: {}", c),
        Event::Schliessen       => println!("Fenster zu"),
    }
}

Das ist wie Unions in C - aber typsicher und ohne unsichere Fallen.

Option - Rusts Antwort auf null

Rust hat kein null. Stattdessen gibt es Option<T>:

enum Option<T> {
    Some(T),
    None,
}

Damit ist im Typ sichtbar, dass ein Wert fehlen kann:

fn erstes_zeichen(s: &str) -> Option<char> {
    s.chars().next()
}

fn main() {
    match erstes_zeichen("Hallo") {
        Some(c) => println!("Erstes Zeichen: {}", c),
        None    => println!("Leerer String"),
    }
}

Kein Null-Pointer-Crash moeglich - der Compiler zwingt dich, den None-Fall zu behandeln.

Result<T, E> - Fehlerbehandlung

Fuer Operationen, die fehlschlagen koennen:

enum Result<T, E> {
    Ok(T),
    Err(E),
}

Beispiel:

fn teile(a: f64, b: f64) -> Result<f64, String> {
    if b == 0.0 {
        Err(String::from("Division durch Null"))
    } else {
        Ok(a / b)
    }
}

fn main() {
    match teile(10.0, 3.0) {
        Ok(ergebnis) => println!("= {}", ergebnis),
        Err(fehler)  => println!("Fehler: {}", fehler),
    }
}

match - exhaustives Pattern Matching

match ist das Schweizer Taschenmesser fuer Alternativen. Zwei Dinge, die match besonders machen:

  1. Exhaustiv: Der Compiler meckert, wenn du einen Fall vergisst.
  2. Pattern Matching: Du kannst auf Strukturen matchen.
let zahl = 7;

let beschreibung = match zahl {
    0           => "Null",
    1 | 2 | 3   => "Klein",
    4..=9       => "Mittel",
    _           => "Gross",
};

Destructuring

struct Punkt { x: i32, y: i32 }

let p = Punkt { x: 0, y: 5 };

match p {
    Punkt { x: 0, y: 0 }  => println!("Ursprung"),
    Punkt { x: 0, y }     => println!("Auf Y-Achse bei {}", y),
    Punkt { x, y: 0 }     => println!("Auf X-Achse bei {}", x),
    Punkt { x, y }        => println!("({}, {})", x, y),
}

Guards

Zusaetzliche Bedingungen mit if:

let zahl = -3;

match zahl {
    n if n < 0  => println!("Negativ"),
    0           => println!("Null"),
    n if n > 0  => println!("Positiv"),
    _           => unreachable!(),
}

if let als kurze Alternative

Wenn dich nur ein Fall interessiert:

let ergebnis = erstes_zeichen("Hallo");

if let Some(c) = ergebnis {
    println!("Erstes Zeichen: {}", c);
}

Und while let fuer Schleifen:

let mut stack = vec![1, 2, 3];
while let Some(top) = stack.pop() {
    println!("{}", top); // 3, 2, 1
}

Zusammenfassung

  • Structs buendeln Daten. Methoden kommen via impl.
  • Enums modellieren Alternativen - mit oder ohne Daten pro Variante.
  • Option ersetzt null.
  • Result<T, E> ist Rusts Fehler-Typ.
  • match ist exhaustiv und erlaubt Destructuring, Guards und Ranges.
  • if let / while let sind die kurzen Varianten.

Damit hast du die Kern-Werkzeuge von Rust beisammen. Im weiteren Kurs vertiefen wir Vektoren, Strings, Fehlerbehandlung mit ? und Traits. Viel Erfolg!

Zurรผck zum Rust Kurs