Einleitung: Warum sagt Go nicht „enum“?
\n\nWenn Sie von Python, Java oder C# zu Go kommen, haben Sie wahrscheinlich einen leichten Kulturschock erlebt: Go hat kein Schlüsselwort enum. Keine eingebauten Aufzählungen mit automatischer Nummerierung, keine Methoden .name() oder .valueOf(). Nur Sie, der Compiler und … Konstanten.
Die Frage „What is an idiomatic way of representing enums in Go?“ ist eine der beliebtesten auf Stack Overflow im Bereich Golang (tausende Aufrufe). Und das ist kein Zufall. Jeder Anfänger stößt früher oder später auf die Wand: „Wie erstelle ich ein Enum, das Go-typisch ist?“.
\n\nIn diesem Artikel geben wir nicht nur eine Antwort. Wir schauen unter die Haube: wie Go Konstanten verarbeitet, was iota ist und warum der Compiler zwei verschiedene int-Werte nicht als unterschiedliche Typen betrachtet. Und am Ende erfahren Sie, wie Sie ein Enum sicher, praktisch und elegant gestalten.
Das Problem: Warum sind einfache Konstanten schlecht?
\n\nDas Erste, was einem in den Sinn kommt, ist, mehrere Konstanten zu deklarieren:
\n\nconst (\n StatusActive = 1\n StatusInactive = 2\n StatusPending = 3\n)\n\nDas scheint zu funktionieren. Aber es gibt einen Haken: Jede Funktion, die ein int akzeptiert, schluckt den Wert 999 oder -42. Der Compiler sagt kein Wort. Das ist keine Aufzählung – das sind nur Zahlen mit Namen.
Ein echtes Enum sollte:
\n- \n
- Die Menge der zulässigen Werte einschränken; \n
- Typsicher sein (man kann kein
intdort übergeben, wo ein Status erwartet wird); \n - Lesbar und leicht erweiterbar sein. \n
Go löst diese Aufgabe idiomatisch: durch benutzerdefinierte Typen und iota.
Der idiomatische Weg: Typ + iota
\n\nSo sieht die kanonische Antwort aus dem besagten Stack Overflow aus:
\n\ntype Status int\n\nconst (\n StatusActive Status = iota // 0\n StatusInactive // 1\n StatusPending // 2\n StatusDeleted // 3\n)\n\nWas ist hier passiert?
\n- \n
type Status int– wir haben einen neuen benannten Typ erstellt. Ja, unter der Haube ist es einint, aber für den Compiler sindStatusundintverschiedene Dinge. \n iotaist ein Generator für aufeinanderfolgende ganze Zahlen, der nur innerhalb einesconst-Blocks funktioniert. Jede neue Zeile erhöht den Wert um 1. \n
Jetzt wird eine Funktion, die ein Status akzeptiert, nicht kompiliert, wenn Sie versuchen, ihr ein normales int zu übergeben:
func Process(s Status) {\n // ...\n}\n\nfunc main() {\n Process(StatusActive) // OK\n Process(1) // Kompilierungsfehler! Cannot use 1 (type int) as type Status\n}\n\nDas ist Typsicherheit – der erste und wichtigste Grund, benutzerdefinierte Typen für Enums zu verwenden.
\n\nWarum iota und nicht manuelle Nummerierung?
\n\nStellen Sie sich vor, Sie fügen einen neuen Status in der Mitte der Liste hinzu. Bei manueller Nummerierung müssten Sie alle nachfolgenden Werte umschreiben. iota macht das automatisch. Das ist nicht nur praktisch – es ist idiomatisch.
Unter der Haube: Wie Go ein Enum sieht
\n\nLassen Sie uns verstehen, was während der Kompilierung tatsächlich passiert.
\n\n1. iota ist kein Runtime-Konstrukt
\n\niota existiert während der Programmausführung nicht. Es ist ein Werkzeug des Präprozessors (genauer gesagt, Teil des Paketcompilers). Während der Kompilierung werden alle iota durch konkrete Zahlen ersetzt. Im resultierenden Binärprogramm sehen Sie einfach 0, 1, 2.
Sie können dies mit go tool compile überprüfen oder einfach den Assembler-Code ansehen:
// Quellcode\nconst (\n A