Structs, Enums & match
Eigene Typen in Rust: Structs fuer zusammengesetzte Daten, Enums fuer Alternativen und match fuer exhaustives Pattern Matching.
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= schreibtself= 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:
- Exhaustiv: Der Compiler meckert, wenn du einen Fall vergisst.
- 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!