Structs
#[derive(Debug, Clone, PartialEq)] // Debug-Ausgabe, Kopieren, Vergleichen automatisch
struct Person {
name: String,
alter: u32,
}
impl Person {
fn neu(name: &str, alter: u32) -> Self { // "Konstruktor" (Konvention: new)
Person { name: name.to_string(), alter }
}
fn vorstellen(&self) -> String { // &self: leiht sich die Person
format!("{} ({})", self.name, self.alter)
}
fn geburtstag(&mut self) { // &mut self: ändert die Person
self.alter += 1;
}
}
fn main() {
let mut mia = Person::neu("Mia", 17);
mia.geburtstag();
println!("{}", mia.vorstellen());
println!("{mia:?}");
println!("{mia:#?}");
let kopie = Person { name: "Tom".into(), ..mia.clone() }; // Struct-Update-Syntax
println!("{} {}", kopie.vorstellen(), mia == kopie);
}Ausgabe
Mia (18)
Person { name: "Mia", alter: 18 }
Person {
name: "Mia",
alter: 18,
}
Tom (18) falseimpl Typ { ... }fügt Methoden hinzu- Das erste Argument ist
self:&self(lesen),&mut self(ändern),self(verbrauchen) - Funktionen ohne
selfsind "assoziierte Funktionen", aufgerufen mitPerson::neu(...) #[derive(...)]erzeugt Standard-Implementierungen
Weitere Formen: Tupel-Structs (struct Meter(f64);) und Unit-Structs (struct Marker;).
Enums: Varianten mit Daten
Ein Rust-enum ist viel mächtiger als in C: Jede Variante kann eigene Daten tragen:
#[derive(Debug)]
enum Form {
Kreis(f64), // ein Wert
Rechteck { breite: f64, hoehe: f64 }, // benannte Felder
Punkt, // keine Daten
}
impl Form {
fn flaeche(&self) -> f64 {
match self {
Form::Kreis(r) => std::f64::consts::PI * r * r,
Form::Rechteck { breite, hoehe } => breite * hoehe,
Form::Punkt => 0.0,
}
}
}
enum Nachricht {
Beenden,
Bewege { x: i32, y: i32 },
Schreibe(String),
}
fn main() {
let formen = [Form::Kreis(1.0), Form::Rechteck { breite: 3.0, hoehe: 4.0 }, Form::Punkt];
for f in &formen {
println!("{f:?}: {:.2}", f.flaeche());
}
let nachrichten = vec![Nachricht::Schreibe("Hi".into()), Nachricht::Bewege { x: 1, y: -1 }, Nachricht::Beenden];
for n in nachrichten {
match n {
Nachricht::Beenden => println!("Ende"),
Nachricht::Bewege { x, y } => println!("bewege um {x}/{y}"),
Nachricht::Schreibe(text) => println!("Text: {text}"),
}
}
}Ausgabe
Kreis(1.0): 3.14
Rechteck { breite: 3.0, hoehe: 4.0 }: 12.00
Punkt: 0.00
Text: Hi
bewege um 1/-1
EndeMit match prüft der Compiler, dass du alle Varianten behandelst. Fügst du später eine hinzu, zeigt er dir jede Stelle, die nachgezogen werden muss.
Option statt null
Rust hat kein null. Für "vielleicht ein Wert" gibt es Option<T>:
fn main() {
let namen = vec!["Mia", "Tom"];
let erster: Option<&&str> = namen.first();
let nummer: Option<i32> = Some(5);
let nichts: Option<i32> = None;
match nummer {
Some(n) => println!("Wert {n}"),
None => println!("kein Wert"),
}
// Bequeme Methoden
println!("{}", nichts.unwrap_or(0));
println!("{}", nummer.map(|n| n * 2).unwrap_or(-1));
println!("{:?} {:?}", nummer.filter(|n| *n > 10), nummer.and_then(|n| if n > 0 { Some(n * 10) } else { None }));
println!("{} {}", nummer.is_some(), nichts.is_none());
println!("{:?}", erster);
if let Some(n) = nummer { println!("if let: {n}"); }
let verschachtelt = namen.get(5).map(|s| s.len()).unwrap_or_default();
println!("{verschachtelt}");
let zahlen = ["1", "x", "3"];
let gueltig: Vec<i32> = zahlen.iter().filter_map(|s| s.parse().ok()).collect();
println!("{gueltig:?}");
}Ausgabe
Wert 5
0
10
None Some(50)
true true
Some("Mia")
if let: 5
0
[1, 3]Der Compiler zwingt dich, den Fall None zu behandeln. Damit verschwindet die berüchtigte Fehlerklasse "Null-Zeiger-Zugriff". unwrap() und expect("Grund") liefern den Wert oder lösen eine Panic aus, nutze sie nur in Tests, Beispielen oder wenn der Fall unmöglich ist.
| Methode | Wirkung |
|---|---|
unwrap_or(x) | Wert oder Ersatz |
unwrap_or_else(|| ...) | Wert oder berechneter Ersatz |
map(f) | Wert umwandeln, None bleibt None |
and_then(f) | verkettet Funktionen, die Option liefern |
ok_or(fehler) | Option in Result verwandeln |
? | im Option-Rückgabe: bei None sofort None zurückgeben |
Traits ableiten und Display
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
struct Punkt {
x: f64,
y: f64,
}
impl fmt::Display for Punkt {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "({}|{})", self.x, self.y)
}
}
fn main() {
let p = Punkt { x: 1.5, y: 2.0 };
let q = p; // Copy: p bleibt gültig
println!("{p} {q:?} {}", p == q);
println!("{}", p.to_string().len());
}Ausgabe
(1.5|2) Punkt { x: 1.5, y: 2.0 } true
7Merke
structbündelt Felder;implgibt Methoden (&self,&mut self,self) undTyp::neu()-Funktionen#[derive(Debug, Clone, PartialEq)]erzeugt Standardverhaltenenum-Varianten tragen Daten;matcherzwingt Vollständigkeit- Es gibt kein
null:Option<T>mitSome/None - Implementiere
Displaymitwrite!für schöne Ausgaben
Aufgabe
Modelliere eine Zahlungsart als enum mit drei Varianten (Karte, PayPal, Rechnung) und schreibe eine Funktion, die für jede die Gebühr berechnet.