enum Farbcode {
Hex,
Rgb,
}
let hexcolor = Farbcode::Hex;
Enums und Pattern Matching
Enums
Enumarations gibt’s in vielen Programmiersprachen, in Rust scheinen sie aber eine große Rolle einzunehmen. "Enumeration" stimmt eigentlich gar nicht, Enums haben hier nämlich nicht zwangsläufig was mit Zahlen zu tun. Grundsätzlich ist ein "Enum" in Rust näher am "Union" würde ich denken.
Ein einfaches Beispiel für ist der Typ Option<T>
(vergleichbar mit Python oder Java Optional
).
Dieser ist entweder None
oder Some(value: T)
- es kann also ein Wert zusätzlich zur "Definition" beinhalten.
Farbcode
ist also ein im Code benutzbarer Datentyp, genauso wie Farbcode::Hex
.
Wenn eine Funktion nun eine Variable mit Typ Farbcode
erwartet, kann diese Variable sowohl Hex
oder Rgb
sein.
Die Funktion kann dann je nach Typ verschieden funktionieren.
Wie schon erwähnt, kann so ein Enum-Wert auch Werte beinhalten, um das zu machen, schreiben wir den Code einfach um:
enum Farbcode {
Hex(String),
Rgb(u8, u8, u8),
}
// Alternativ:
struct Hex(String);
struct Rgb(u8, u8, u8);
enum Farbcode {
Hex,
Rgb
}
let hexcode = Farbcode::Hex(String::from("00affe"));
let rgbcode = Farbcode::Rgb(125, 255, 255);
Natürlich können die Structs jeder Art sein. Enums sind aber auch selber eine Art Struct. Also können wir für Enums auch Methoden definieren wie für Structs.
impl Farbcode {
fn to_css_string(&self) {
// Methode, die für Hex und Rgb angewendet werden kann
}
}
let rgbcode = Farbcode::Rgb(125, 255, 255);
rgbcode.to_css_string();
Tatsächlich ist damit so etwas wie Vererbung implementierbar. Es gibt zwar keine Attribute, aber da ja auch die internen Structs Methoden haben können, ist eine gewisse Hierarchie erstellbar.
Option<T>
Options hab ich oben schonmal kurz beschrieben. In Rust ist dieser Datentyp sehr wichtig. Die Dokumentation dazu ist hier zu finden und enthält sehr viel Wichtiges und Interessantes.
match
match
ist quasi das switch
von Rust.
Nur kann es auch prüfen, ob eine Variable einem Enum-Typen angehört.
So wie Rust bis jetzt klang, kann wahrscheinlich jedem Datentypen ein "match-Trait" gegeben werden, der dann eine "Zugehörigkeit" (Gleichheit stimmt ja irgendwie nicht) prüfen kann.
Aber ganz einfach: Angenommen wir wollen die Methode to_css_string
von oben implementieren.
Diese Methode muss ja, je nach Typ, völlig unterschiedlich funktionieren.
enum Farbcode {
Hex(String),
Rgb(u8, u8, u8),
}
impl Farbcode {
fn to_css_string(&self) -> String {
match self {
// format! ist offensichtlich ein Pragma, dass Strings erstellt auf die selbe Weise wie println!
Farbcode::Hex(hex) => format!("#{}", hex),
Farbcode::Rgb(r, g, b) => format!("rgb({}, {}, {})", r, g, b),
}
}
}
fn main() {
let hexcode = Farbcode::Hex(String::from("affe00"));
let rgbcode = Farbcode::Rgb(125, 255, 255);
println!("{}", hexcode.to_css_string());
println!("{}", rgbcode.to_css_string());
}
Hier sieht man auch ganz gut, wie im Match dem "Inhalt" des Enums direkt Namen gegeben werden und Tuples auch dekonstruiert.
Im Beispiel ist auch deutlich, dass match
einen Rückgabewert hat, nämlich das, was im Statement(-Block) des jeweiligen Matches zurückgegeben wird.
Vollständigkeit
Entweder muss ein match
eines Enums jede mögliche Variante abgrasen oder es gibt zwei Alternativen.
other
ist quasi das default
von Rust.
Aber auch _
matched alles.
Der Unterschied ist, dass bei other
noch der Inhalt genutzt werden kann, bei _
wird er direkt ignoriert und ist nicht nutzbar.
if let
Dieses if-Konstrukt nutzt man am besten, wenn man nur auf eine einzelne Variante eines Enums prüfen möchte. Letztendlich ist es ganz simpel:
#[derive(Debug)]
enum Muenzwurf {
Kopf,
Zahl,
Seite
}
fn print_wurf(ergebnis: Muenzwurf) {
if let Muenzwurf::Seite = ergebnis {
println!("Das glaub ich nicht! Seite?!");
} else {
println!("Du hast {:?} geworfen.", ergebnis);
}
}
fn main() {
let ergebnis = Muenzwurf::Zahl;
print_wurf(ergebnis); // Du hast Zahl geworfen.
let ergebnis = Muenzwurf::Seite;
print_wurf(ergebnis); // Das glaub ich nicht! Seite?!
}