Enums in Rust
Ein Enum (kurz für „Enumeration“, also Aufzählung) definiert einen Typ, der genau einen Wert aus einer festen Auswahl annehmen kann. Ein Struct sagt: „Dieses Ding hat alle diese Felder.“ Ein Enum sagt dagegen: „Dieses Ding ist genau eine dieser Möglichkeiten.“
Ein Enum definieren und benutzen
Abschnitt betitelt „Ein Enum definieren und benutzen“enum Direction { North, South, East, West,}
fn main() { let heading = Direction::North;
match heading { Direction::North => println!("Richtung Norden"), Direction::South => println!("Richtung Süden"), Direction::East => println!("Richtung Osten"), Direction::West => println!("Richtung Westen"), }}Ausgabe:
Richtung NordenDieses match braucht keinen Auffangzweig mit _. Weil jede mögliche Direction aufgeführt ist, weiß Rust bereits, dass das match vollständig ist.
Enums können Daten enthalten
Abschnitt betitelt „Enums können Daten enthalten“Hier gehen Enums in Rust weiter als in vielen anderen Sprachen: Jede Variante kann ihre eigenen Daten mitbringen.
enum Shape { Circle(f64), // Radius Rectangle(f64, f64), // Breite, Höhe}
fn area(shape: &Shape) -> f64 { match shape { Shape::Circle(radius) => 3.14159 * radius * radius, Shape::Rectangle(width, height) => width * height, }}
fn main() { let circle = Shape::Circle(3.0); let rectangle = Shape::Rectangle(4.0, 5.0);
println!("{:.2}", area(&circle)); println!("{:.2}", area(&rectangle));}Ausgabe:
28.2720.00Shape::Circle enthält einen f64 (den Radius), Shape::Rectangle enthält zwei. match holt diese Daten in jedem Zweig direkt für dich heraus. Du brauchst keinen extra Schritt, um sie „auszupacken“.
Auch Enums können Methoden haben
Abschnitt betitelt „Auch Enums können Methoden haben“Genau wie ein Struct kann ein Enum einen impl-Block mit Methoden haben. In einer Methode schreibst du meistens match self, um für jede Variante zu entscheiden, was passieren soll:
#[derive(Debug)]enum Light { Red, Yellow, Green,}
impl Light { fn seconds(&self) -> u32 { match self { Light::Red => 30, Light::Yellow => 3, Light::Green => 25, } }
fn next(&self) -> Light { match self { Light::Red => Light::Green, Light::Green => Light::Yellow, Light::Yellow => Light::Red, } }}
fn main() { let mut light = Light::Red;
for _ in 0..4 { println!("{:?} für {} Sekunden", light, light.seconds()); light = light.next(); }}Ausgabe:
Red für 30 SekundenGreen für 25 SekundenYellow für 3 SekundenRed für 30 Sekundennext gibt ein ganz neues Light zurück, und light = light.next(); tauscht das alte dagegen aus. Deshalb ist light als mut deklariert. #[derive(Debug)] funktioniert bei Enums genauso wie bei Structs, also gibt {:?} den Namen der Variante aus.
Steckt die Logik in Methoden, bleibt sie direkt beim Typ. Wer ein Light benutzt, kann light.seconds() aufrufen, ohne zu wissen, wie die Zeiten festgelegt werden. Und fügst du später eine neue Variante hinzu, etwa FlashingYellow, zeigt dir der Compiler jedes match, das du anpassen musst.
Option: ein Enum, das du schon benutzt hast
Abschnitt betitelt „Option: ein Enum, das du schon benutzt hast“Der Typ Option, den du auf seiner eigenen Seite genauer kennenlernst, ist einfach ein Enum aus der Standardbibliothek:
enum Option<T> { Some(T), None,}Sobald du Enums verstanden hast, wirkt Option nicht mehr wie eine besondere Syntax. Es ist ein ganz normales Enum mit zwei Varianten, von denen eine zufällig einen Wert enthält.
Warum nicht einfach ein Struct mit einem „Typ“-Feld?
Abschnitt betitelt „Warum nicht einfach ein Struct mit einem „Typ“-Feld?“Du könntest so etwas auch mit einem Struct und einem String-Feld nachbauen, etwa "circle" oder "rectangle". Aber dann hält dich nichts davon ab, aus Versehen "circl" zu tippen. Außerdem müsstest du selbst prüfen, welche Felder für welchen Typ überhaupt gelten. Mit einem Enum kannst du ungültige Kombinationen gar nicht erst hinschreiben, und der Compiler zwingt dich, jede Variante zu behandeln.
Übungen
Abschnitt betitelt „Übungen“1. Münzen zählen
Abschnitt betitelt „1. Münzen zählen“Schreib ein Enum Coin mit den Varianten Penny, Nickel, Dime und Quarter (amerikanische Münzen zu 1, 5, 10 und 25 Cent). Schreib fn value_in_cents(coin: &Coin) -> u32 und rechne dann den Inhalt eines Geldbeutels zusammen: ein Quarter, ein Dime, ein Nickel, ein Penny und noch ein Quarter. Tipp: Leg die Münzen in ein Array und lauf mit einer Schleife über &purse.
Lösung anzeigen
enum Coin { Penny, Nickel, Dime, Quarter,}
fn value_in_cents(coin: &Coin) -> u32 { match coin { Coin::Penny => 1, Coin::Nickel => 5, Coin::Dime => 10, Coin::Quarter => 25, }}
fn main() { let purse = [Coin::Quarter, Coin::Dime, Coin::Nickel, Coin::Penny, Coin::Quarter];
let mut total = 0; for coin in &purse { total += value_in_cents(coin); }
println!("Summe: {total} Cent");}Ausgabe:
Summe: 66 Cent2. Ein Dreieck hinzufügen
Abschnitt betitelt „2. Ein Dreieck hinzufügen“Nimm das Enum Shape von dieser Seite und füge eine Variante Triangle hinzu, die die Längen der drei Seiten enthält. Schreib dann eine Funktion perimeter (der Umfang, also die Strecke einmal ganz herum), die für jede Form funktioniert. Tipp: Der Umfang eines Kreises ist 2.0 * 3.14159 * radius. Beim Dreieck addierst du einfach die drei Seiten.
Lösung anzeigen
enum Shape { Circle(f64), Rectangle(f64, f64), Triangle(f64, f64, f64),}
fn perimeter(shape: &Shape) -> f64 { match shape { Shape::Circle(radius) => 2.0 * 3.14159 * radius, Shape::Rectangle(width, height) => 2.0 * (width + height), Shape::Triangle(a, b, c) => a + b + c, }}
fn main() { let shapes = [ Shape::Circle(1.0), Shape::Rectangle(4.0, 5.0), Shape::Triangle(3.0, 4.0, 5.0), ];
for shape in &shapes { println!("{:.2}", perimeter(shape)); }}Ausgabe:
6.2818.0012.00Lösch zum Ausprobieren mal den Zweig Shape::Triangle aus dem match. Rust weigert sich dann zu kompilieren, weil das match nicht mehr jede Variante abdeckt. So stellt der Compiler sicher, dass du nie einen Fall vergisst.
3. Temperaturen
Abschnitt betitelt „3. Temperaturen“Schreib ein Enum Temperature mit zwei Varianten, die jeweils einen f64 enthalten: Celsius und Fahrenheit. Füge eine Methode to_celsius(&self) -> f64 hinzu und gib Celsius(25.0), Fahrenheit(212.0) und Fahrenheit(50.0) in °C mit einer Nachkommastelle aus. Tipp: Zum Umrechnen nimmst du (f - 32.0) * 5.0 / 9.0.
Lösung anzeigen
enum Temperature { Celsius(f64), Fahrenheit(f64),}
impl Temperature { fn to_celsius(&self) -> f64 { match self { Temperature::Celsius(c) => *c, Temperature::Fahrenheit(f) => (f - 32.0) * 5.0 / 9.0, } }}
fn main() { let readings = [ Temperature::Celsius(25.0), Temperature::Fahrenheit(212.0), Temperature::Fahrenheit(50.0), ];
for t in &readings { println!("{:.1} °C", t.to_celsius()); }}Ausgabe:
25.0 °C100.0 °C10.0 °CWeil self ausgeliehen ist, sind c und f Referenzen (&f64). Mit Referenzen kannst du direkt rechnen. Um aber den reinen Celsius-Wert zurückzugeben, schreibst du *c – das heißt „die Zahl, auf die c zeigt“.
Zusammenfassung
Abschnitt betitelt „Zusammenfassung“- Ein Enum definiert einen Typ, der eine von mehreren benannten Varianten ist:
enum Direction { North, South, ... }. - Varianten können eigene Daten enthalten, und verschiedene Varianten können verschiedene Daten enthalten.
matchzerlegt die Daten eines Enums ganz natürlich und verlangt, dass jede Variante behandelt wird.- Enums können in einem
impl-Block Methoden haben, genau wie Structs. Option(undResult, das später kommt) sind ganz normale Enums aus der Standardbibliothek – keine Magie.
Kurzer Check
Abschnitt betitelt „Kurzer Check“Kurzer Check
1. Was kann jede Variante eines Enums in Rust, anders als in vielen anderen Sprachen?
2. Warum braucht das match im Direction-Beispiel keinen Auffangzweig mit _?
3. Wie ist Option<T> in der Standardbibliothek tatsächlich umgesetzt?
Punkte: 0 / 3
Weiterlesen
Abschnitt betitelt „Weiterlesen“- Eine Aufzählung definieren – Das Rust-Buch: Varianten mit Daten, Methoden auf Enums und warum Rust
Optionstatt null hat. - Das Kontrollflusskonstrukt match – Das Rust-Buch:
matchauf Enums ausführlich erklärt, mit einem Beispiel zum Sortieren von Münzen und Auffang-Mustern. - Enums – Rust by Example (auf Englisch): ausführbare Beispiele, auch mit Varianten mit benannten Feldern wie
Click { x: i64, y: i64 }. - Enumerations – The Rust Reference (auf Englisch): die vollständigen Regeln, etwa wie du Varianten feste Zahlenwerte gibst.
Option– Standardbibliothek (auf Englisch): die echte Definition des Enums, das du am häufigsten benutzen wirst, mit all seinen Hilfsmethoden.