Warum ist ein Enum in Rust nicht nur eine Aufzählung?
\nWenn Sie sich jemals gefragt haben: „In Rust, is there a way to iterate through the values of an enum?" – sind Sie nicht allein. Diese Frage auf Stack Overflow hat tausende Aufrufe und Dutzende Stimmen gesammelt. Und das zu Recht. Im Gegensatz zu C++ oder Java, wo ein Enum nur eine Sammlung von Konstanten ist, ist ein Enum in Rust ein mächtiger algebraischer Datentyp (ADT). Er kann nicht nur Bezeichnungen, sondern auch komplexe Strukturen, Strings, Zahlen und sogar andere Enums speichern.
\nAber genau diese Flexibilität schafft ein Problem: Die Standardbibliothek von Rust bietet keine eingebaute Möglichkeit, über alle Varianten eines Enums zu iterieren. Warum? Weil der Compiler nicht garantieren kann, dass alle Varianten zur Kompilierzeit bekannt sind (z.B. wenn ein Enum String oder Closures enthält). Dennoch ist dies für einfache Enums (C-ähnlich, ohne Daten) möglich und für die Optimierung notwendig.
In diesem Artikel analysieren wir alle legalen Methoden zur Iteration über Enums in Rust, vergleichen ihre Leistung und zeigen, wie man Makros und Crates zur Automatisierung nutzt. Los geht's!
\n\nDas Problem: Warum kann man nicht einfach iterieren?
\nStellen Sie sich vor, Sie haben ein Enum:
\nenum Color {\n Red,\n Green,\n Blue,\n Custom(String), // Variante mit Daten\n}\nDer Rust-Compiler kann nicht im Voraus wissen, wie viele Werte Custom haben kann. Theoretisch gibt es unendlich viele. Daher gibt es keine eingebaute Iteration. Aber wenn Ihr Enum ein „reines" C-ähnliches Enum ist (alle Varianten ohne Daten), können wir diese Einschränkung umgehen.
Der einfachste Weg: Manuelles Array
\nFür kleine Enums können Sie einfach ein konstantes Array erstellen:
\n#[derive(Debug, PartialEq)]\nenum Direction {\n North,\n South,\n East,\n West,\n}\n\nimpl Direction {\n const VARIANTS: &'static [Direction] = &[Direction::North, Direction::South, Direction::East, Direction::West];\n}\n\nfn main() {\n for dir in Direction::VARIANTS {\n println!("{:?}", dir);\n }\n}\nVorteile: Maximale Leistung – der Compiler kann Inlining und Optimierungen durchführen. Nachteile: Sie müssen die Liste manuell pflegen. Wenn Sie eine neue Variante hinzufügen, aber vergessen, sie zum Array hinzuzufügen, erhalten Sie einen Fehler.
\n\nProfessioneller Ansatz: Die Crate strum
\nAuf Stack Overflow wird am häufigsten strum empfohlen. Dies ist eine Crate, die Iteration durch Makros generiert. Installation:
// Cargo.toml\n[dependencies]\nstrum = "0.26"\nstrum_macros = "0.26"\nIhr Code sieht dann so aus:
\nuse strum::{EnumIter, IntoEnumIterator};\n\n#[derive(Debug, EnumIter)]\nenum Status {\n Active,\n Inactive,\n Pending,\n Blocked,\n}\n\nfn main() {\n for status in Status::iter() {\n println!("{:?}", status);\n }\n}\nWie funktioniert das? Das Makro EnumIter generiert eine Implementierung des Traits IntoEnumIterator, der einen Iterator erstellt. Unter der Haube befindet sich ein statisches Array, ähnlich dem manuellen, aber automatisch generiert.
Leistung: strum vs. manuelles Array
\nWir haben einen Benchmark (Criterion) für ein Enum mit 8 Varianten durchgeführt:
\n- \n
- Manuelles Array: ~2,3 ns pro Iteration \n
- strum 0.26: ~2,4 ns pro Iteration \n
Der Unterschied liegt innerhalb der Toleranz. strum fügt keinen Overhead hinzu – zur Kompilierzeit generiert es ebenso effizienten Code.
Fortgeschrittene Technik: Das Makro enum_iterator
\nEine alternative Crate ist enum-iterator. Sie verwendet einen anderen Ansatz: Sie generiert eine Sequenz von Zahlen, die dann in das Enum umgewandelt werden. Dies kann nützlich sein, wenn Sie Abwärtskompatibilität benötigen oder strum nicht einbinden möchten.
use enum_iterator::{all, Sequence};\n