Das Keyword const gibt einem Wert einen Namen, den der Compiler schon beim Build kennt, etwa einem Timeout, einem Header-Namen oder einer Größenbegrenzung. Eine Go-Konstante kann nur einen booleschen Wert, eine Zahl, einen String oder eine Rune enthalten, und sie kann sich nie ändern, während das Programm läuft. Die meisten Konstanten sind untypisiert. Sie haben also keinen festen Typ, bis du sie verwendest, und deshalb funktioniert const maxBodyBytes = 1 << 20 ohne Konvertierung als int, int64 oder float64. Innerhalb eines const-Blocks zählt der vordeklarierte Bezeichner iota ab 0 aufwärts, und so schreibt man in Go Enums (Go spec).
Kurzfassung
const name = valuedeklariert eine Konstante. Ein Blockconst ( ... )deklariert mehrere. So werden zusammengehörige Konstanten in Go üblicherweise gruppiert.- Konstanten können boolesche Werte, Zahlen, Strings oder Runes sein. Slices, Maps, Structs und das Ergebnis eines Funktionsaufrufs wie
time.Now()können keine Konstanten sein. - Eine untypisierte Konstante wie
5oder"GET"nimmt den Typ an, den ihr Kontext braucht. Aus diesem Grund kompiliert5 * time.Second, währendtimeout * time.Secondmit einerint-Variable nicht kompiliert. - Untypisierte Konstanten sind exakt. Der Compiler rechnet mit ihnen in beliebiger Genauigkeit und meldet sich erst, wenn ein Wert nicht in den Typ passt, in dem er am Ende landet.
iotabeginnt in jedemconst-Block bei 0 und steigt pro Zeile um eins._überspringt einen Wert,iota + 1beginnt bei 1, und1 << iotaerzeugt Bit-Flags.- Go hat kein Keyword
enum. Ein Enum besteht aus einem benannten Typ, einemconst-Block mitiotaund meist einer MethodeString(), die das Toolstringergenerieren kann. - Du kannst weder die Adresse einer Konstante nehmen noch ihr etwas zuweisen. Werte, die zur Laufzeit berechnet werden, und Lookup-Tabellen wie Maps gehören in eine
var.
Wie deklariert man eine Konstante in Go?
Schreib const, einen Namen und einen Wert. Für mehrere zusammengehörige Konstanten verwendest du einen gruppierten Block mit Klammern:
example.gogopackage main import ( "fmt" "net/http" "time" ) const maxBodyBytes = 1 << 20 // 1 MiB const ( readTimeout = 5 * time.Second writeTimeout = 10 * time.Second idleTimeout = 2 * time.Minute ) const ( headerRequestID = "X-Request-ID" headerRetryAfter = "Retry-After" ) func main() { srv := &http.Server{ Addr: ":8080", ReadTimeout: readTimeout, WriteTimeout: writeTimeout, IdleTimeout: idleTimeout, } fmt.Println(srv.ReadTimeout, srv.WriteTimeout, srv.IdleTimeout) }
Das gibt 5s 10s 2m0s aus. Bei einfachen Werten wie diesen ist die Gruppierung nur für Leser da. Jeder Name im Block ist eine eigenständige Konstante, und der Block hält die Timeouts des Servers an einer Stelle zusammen, an der ein Reviewer sie vergleichen kann.
Für Konstanten gelten dieselben Regeln für Scope und Sichtbarkeit wie für Variablen. Eine Konstante auf Package-Ebene ist in jeder Datei des Packages sichtbar, und sie wird exportiert, wenn ihr Name mit einem Großbuchstaben beginnt, wie bei http.StatusNotFound oder time.RFC3339. Das Go-Keyword package erklärt, wie exportierte Namen über Package-Grenzen hinweg funktionieren. Eine Konstante, die innerhalb einer Funktion deklariert ist, existiert nur in dieser Funktion. Der Go-Stil verwendet für Konstanten wie für jeden anderen Namen MixedCaps. Idiomatisch schreibt man also maxBodyBytes oder MaxBodyBytes, nicht MAX_BODY_BYTES.
Ein const-Block hat eine Regel, die ein var-Block nicht hat. Hat eine Zeile einen Namen, aber weder Typ noch Wert, wiederholt sie Typ und Ausdruck der vorherigen Zeile. Für sich allein gibt das nur mehreren Namen denselben Wert, und das willst du selten. Zusammen mit iota nummeriert so ein einziger Ausdruck eine ganze Liste von Konstanten.
Welche Werte können in Go Konstanten sein?
Nur boolesche Werte, Zahlen, Strings und Runes. Zu den Zahlen gehören Ganzzahlen, Gleitkommazahlen und komplexe Zahlen, und eine Rune ist eine Ganzzahl, die für einen Unicode-Codepoint steht. Alles andere kompiliert nicht, auch ein Wert, der in der Praxis feststeht, aber zur Laufzeit berechnet werden muss:
example.gogopackage main import "time" const startedAt = time.Now()
example.texttext./main.go:5:19: time.Now() (value of struct type time.Time) is not constant
Zusammengesetzte Werte werden genauso abgelehnt. const allowedMethods = []string{"GET", "POST"} scheitert mit []string{…} (value of type []string) is not constant. Slices, Maps, Structs, Pointer, Channels und Funktionen können keine Konstanten sein, selbst wenn jedes Element ein Literal ist.
Benannte Typen, die auf einer Zahl, einem String oder einem booleschen Wert aufbauen, sind erlaubt. time.Duration ist als int64 definiert, also ist const pollInterval = 500 * time.Millisecond eine Konstante vom Typ time.Duration mit dem Wert 500ms. Dasselbe gilt für deine eigenen Typen wie type LogLevel int, und darauf bauen Enums auf.
Einige eingebaute Funktionen liefern ebenfalls Konstanten, wenn ihr Argument das zulässt. len eines Arrays ist eine Konstante, weil die Länge eines Arrays Teil seines Typs ist. Auch len("Retry-After") auf einer String-Konstante ist eine Konstante. unsafe.Sizeof, unsafe.Alignof und unsafe.Offsetof sind Konstanten, sobald der Typ des Arguments eine feste Größe hat, also kompiliert const headerSize = unsafe.Sizeof(int64(0)) und hat den Wert 8. len eines Slice ist keine Konstante, da die Länge eines Slice erst zur Laufzeit bekannt ist.
Was ist der Unterschied zwischen typisierten und untypisierten Konstanten in Go?
Eine typisierte Konstante hat in ihrer Deklaration einen Typ. Eine untypisierte Konstante hat keinen und nimmt den Typ an, den der umgebende Code braucht:
example.gogoconst maxRetries int32 = 3 // typed: always an int32 const maxBodyBytes = 1 << 20 // untyped: becomes whatever type it is used as
Die typisierte folgt den normalen Zuweisungsregeln und passt deshalb ohne Konvertierung nicht in einen int:
example.gogovar attempts int = maxRetries
example.texttext./main.go:6:21: cannot use maxRetries (constant 3 of type int32) as int value in variable declaration
Die untypisierte passt gleichermaßen in var limit int64 = maxBodyBytes, var limit int = maxBodyBytes und float64(maxBodyBytes). Die Standardbibliothek lässt deshalb die meisten ihrer numerischen Konstanten untypisiert, etwa math.MaxInt64 und math.Pi.
Eine untypisierte Konstante hat trotzdem einen Standardtyp. Er kommt zum Einsatz, wenn sonst nichts den Typ festlegt, wie bei x := 1.5 oder wenn die Konstante an fmt.Println übergeben wird:
| Untypisierte Konstante | Standardtyp |
|---|---|
true, false | bool |
42, 1 << 20 | int |
1.5, 1e9 | float64 |
'a' | rune (ein Alias für int32) |
2i | complex128 |
"GET" | string |
Warum kompiliert 5 * time.Second, aber timeout * time.Second nicht?
Weil 5 eine untypisierte Konstante ist und timeout eine Variable. Go konvertiert nie von selbst zwischen numerischen Typen, aber eine untypisierte Konstante kann jeden numerischen Typ annehmen, in den ihr Wert passt. In 5 * time.Second wird die 5 zu einer time.Duration, und das Ergebnis ist eine time.Duration von fünf Sekunden.
Eine Variable hat bereits einen Typ, deshalb scheitert dieselbe Multiplikation, wenn die Zahl aus der Konfiguration kommt:
example.gogopackage main import ( "net/http" "os" "strconv" "time" ) func main() { timeout, _ := strconv.Atoi(os.Getenv("TIMEOUT_SECONDS")) client := &http.Client{Timeout: timeout * time.Second} _ = client }
example.texttext./main.go:12:34: invalid operation: timeout * time.Second (mismatched types int and "time".Duration)
timeout ist ein int, und time.Second ist eine time.Duration. Die Lösung ist eine explizite Konvertierung, time.Duration(timeout) * time.Second. Eine untypisierte Konstante mit demselben Wert kommt ohne aus. Mit const retryDelay = 3 ergibt retryDelay * time.Second also 3s.
Wie genau sind untypisierte Konstanten?
Untypisierte numerische Konstanten sind exakt. Die Spezifikation verlangt, dass Compiler Integer-Konstanten mit mindestens 256 Bit darstellen und Gleitkommakonstanten mit einer Mantisse von mindestens 256 Bit genau halten, und der Standard-Go-Compiler erlaubt bis zu 512 Bit. math.Pi ist mit mehr Stellen ausgeschrieben, als ein float64 fassen kann, und wird erst gerundet, wenn es in einer float64- oder float32-Variable landet.
Diese Genauigkeit erlaubt Zwischenwerte, die für jeden Go-Typ zu groß sind, solange das Endergebnis passt. const maxOffset = 1 << 100 kompiliert, und maxOffset >> 98 ist die Konstante 4. Der Fehler kommt erst, wenn der Wert zu einem konkreten Typ werden muss:
example.gogopackage main import "fmt" const maxOffset = 1 << 100 func main() { fmt.Println(maxOffset) }
example.texttext./main.go:8:14: cannot use maxOffset (untyped int constant 1267650600228229401496703205376) as int value in argument to fmt.Println (overflows)
Weil der Compiler jeden Konstantenwert kennt, finden diese Bereichsprüfungen schon beim Build statt, und kein Wert läuft zur Laufzeit stillschweigend über. Noch ein paar Beispiele:
| Code | Compilerfehler |
|---|---|
var uploadLimit int32 = 1 << 40 | cannot use 1 << 40 (untyped int constant 1099511627776) as int32 value in variable declaration (overflows) |
const maxConns uint8 = 300 | cannot use 300 (untyped int constant) as uint8 value in constant declaration (overflows) |
var workers int = 2.5 | cannot use 2.5 (untyped float constant) as int value in variable declaration (truncated) |
Die Prüfung betrifft nur den Wert. var workers int = 2.0 kompiliert, weil 2.0 eine untypisierte Gleitkommakonstante ist, deren Wert eine ganze Zahl ist.
Was ist iota in Go?
iota ist ein vordeklarierter Bezeichner, der nur innerhalb einer const-Deklaration eine Bedeutung hat. Für die erste Konstanten-Spec eines const-Blocks ist er 0, und mit jeder folgenden Spec steigt er um eins. Eine Spec ist eine Zeile, die Namen deklariert, Leerzeilen und Kommentare zählen also nicht mit. Zusammen mit der impliziten Wiederholung des vorherigen Ausdrucks reichen ein Typ und ein einziges iota, um eine ganze Liste durchzunummerieren:
example.gogotype LogLevel int const ( LevelDebug LogLevel = iota // 0 LevelInfo // 1 LevelWarn // 2 LevelError // 3 )
LevelInfo, LevelWarn und LevelError haben weder einen eigenen Typ noch einen eigenen Wert, also wiederholt jede von ihnen LogLevel = iota mit dem nächsten Wert von iota. Jede Konstante in der Liste hat den Typ LogLevel.
So zählt iota:
iotawird am Anfang jedesconst-Blocks auf 0 zurückgesetzt. Zwei getrennte Blöcke beginnen beide bei 0.- Es zählt Specs, keine Namen. Zwei Konstanten, die in derselben Zeile deklariert sind, sehen dasselbe
iota. - Eine Zeile mit
_zählt trotzdem mit, der Blank Identifier überspringt also einen Wert. iotaist eine untypisierte Integer-Konstante und funktioniert deshalb in jedem konstanten Ausdruck, etwaiota + 1oder1 << iota.
Wie startet man iota bei 1 oder überspringt einen Wert?
Addiere eins, oder wirf den ersten Wert mit _ weg. Ein Start bei 1 hält den Nullwert für die Bedeutung „nicht gesetzt“ frei:
example.gogotype Priority int const ( PriorityLow Priority = iota + 1 // 1 PriorityNormal // 2 PriorityHigh // 3 )
Überspringen mit _ passt, wenn der erste Wert keine Bedeutung hat. Die Größeneinheiten unten überspringen iota == 0, weil 1 << 0 ein Byte wäre:
example.gogotype ByteSize int64 const ( _ = iota // skip 0 KB ByteSize = 1 << (10 * iota) MB GB )
KB ist 1 << 10, also 1024, MB ist 1 << 20 und GB ist 1 << 30. Jede Zeile wiederholt ByteSize = 1 << (10 * iota) mit einem größeren iota.
Wie definiert man Bit-Flags mit iota?
Verschieb 1 um iota Stellen nach links. Jede Konstante bekommt dann ihr eigenes Bit, und du kombinierst sie mit |:
example.gogopackage main import "fmt" type Permission uint8 const ( PermRead Permission = 1 << iota // 1 PermWrite // 2 PermDelete // 4 ) func main() { perm := PermRead | PermWrite fmt.Println(perm&PermWrite != 0) // true fmt.Println(perm&PermDelete != 0) // false }
Die Standardbibliothek verwendet dasselbe Muster. log.Ldate, log.Ltime und log.Lshortfile sind 1 << iota-Flags, die du in log.SetFlags(log.LstdFlags | log.Lshortfile) kombinierst.
Hat Go Enums?
Nicht als eigenes Sprachfeature. Es gibt kein Keyword enum. Go baut Enums aus drei Teilen, die es schon hat: einem benannten Typ, einem const-Block mit iota und einer Methode String(), damit die Werte als Namen statt als Zahlen ausgegeben werden:
example.gogopackage main import "fmt" type LogLevel int const ( LevelDebug LogLevel = iota LevelInfo LevelWarn LevelError ) func (l LogLevel) String() string { switch l { case LevelDebug: return "debug" case LevelInfo: return "info" case LevelWarn: return "warn" case LevelError: return "error" default: return fmt.Sprintf("LogLevel(%d)", int(l)) } } func main() { fmt.Println(LevelWarn, LogLevel(9)) fmt.Printf("level=%v\n", LevelError) }
Das gibt warn LogLevel(9) und danach level=error aus. fmt ruft String() auf, wenn es einen Wert mit Println, %v oder %s ausgibt. Der default-Fall macht aus einem Wert außerhalb der Liste, wie LogLevel(9), etwas Lesbares.
Wie generiert man String-Methoden mit stringer?
Diesen switch von Hand zu schreiben wird schnell mühsam, und er veraltet, wenn jemand eine Konstante hinzufügt und die Methode vergisst. Das Tool stringer vom Go-Team generiert ihn. Füge neben dem Typ eine go:generate-Direktive ein und führe go generate aus:
example.gogopackage main import "fmt" //go:generate go run golang.org/x/tools/cmd/stringer@latest -type=OrderStatus -trimprefix=OrderStatus type OrderStatus int const ( OrderStatusUnknown OrderStatus = iota OrderStatusPending OrderStatusPaid OrderStatusShipped OrderStatusRefunded ) func main() { fmt.Println(OrderStatusPaid, OrderStatus(42)) }
go generate schreibt orderstatus_string.go mit einer Methode String(), und das Programm gibt Paid OrderStatus(42) aus. -trimprefix entfernt das gemeinsame Präfix aus den Namen. Die generierte Datei enthält außerdem eine kleine Prüfung zur Compile-Zeit. Ändert sich der Wert einer bestehenden Konstante und wurde die Datei nicht neu generiert, scheitert der Build mit einem Fehler wie invalid argument: index 4 out of bounds [0:1]. Eine Konstante, die am Ende der Liste hinzukommt, fällt dabei nicht auf. Sie wird als OrderStatus(5) ausgegeben, bis du go generate erneut ausführst. Deshalb führen viele Teams es in der CI aus und lassen den Build scheitern, wenn sich die generierten Dateien ändern.
Warum sollte der Nullwert eines Enums Unknown sein?
Weil jede Go-Variable mit ihrem Nullwert beginnt, und bei einem Enum auf int-Basis ist das 0. Ein Struct Order, das aus JSON ohne Feld status dekodiert oder mit var o Order deklariert wurde, hat den Status 0. Ist 0 gleich OrderStatusPending, sieht ein fehlender Wert wie eine echte offene Bestellung aus. Setzt du ein explizites OrderStatusUnknown an den Anfang oder beginnst bei iota + 1, bedeutet der Nullwert „nicht gesetzt“, und der Code kann ihn ablehnen.
Prüft Go, ob ein switch jeden Enum-Wert abdeckt?
Nein. Ein Enum-Typ ist immer noch ein gewöhnlicher Integer-Typ, der Compiler weiß also nicht, welche Werte gültig sind. var level LogLevel = 9 kompiliert, weil 9 eine untypisierte Konstante ist, die in einen int passt. Ein switch, der nur einen Teil der Werte behandelt, kompiliert ebenfalls ohne Warnung.
Teams, die diese Prüfung wollen, setzen den Linter exhaustive ein, der in golangci-lint enthalten ist. Er meldet jeden switch auf einem Enum-Typ, dem eine der Konstanten des Typs fehlt. Ein default-Fall wie der in der Methode String() oben behandelt Werte außerhalb der Liste zur Laufzeit.
Wann verwendet man in Go const und wann var?
Verwende const für Werte, die du schon beim Schreiben des Codes kennst und die sich nie ändern: Timeouts, Limits, Header-Namen, Enum-Werte. Verwende var für alles, was zur Laufzeit berechnet, aus der Konfiguration gelesen oder aus einem zusammengesetzten Typ gebaut wird.
Du kannst nicht die Adresse einer Konstante nehmen, weil eine Konstante in keiner Variable gespeichert ist:
example.gogop := &maxBodyBytes
example.texttext./main.go:6:8: invalid operation: cannot take address of maxBodyBytes (untyped int constant 1048576)
Das begegnet dir bei APIs, die Pointer auf optionale Felder erwarten. Seit Go 1.26 akzeptiert new einen Ausdruck, also gibt new(maxBodyBytes) einen *int zurück, der auf eine Kopie des Werts zeigt. In älteren Versionen kopierst du die Konstante zuerst in eine Variable und nimmst deren Adresse.
Zuweisen kannst du einer Konstante ebenfalls nichts. maxBodyBytes = 2 << 20 scheitert mit cannot assign to maxBodyBytes (neither addressable nor a map index expression).
Ungenutzte Konstanten kompilieren. Eine ungenutzte lokale Variable ist ein Fehler, declared and not used: retries, eine ungenutzte Konstante wird dagegen akzeptiert, selbst innerhalb einer Funktion. Linter wie staticcheck melden ungenutzte, nicht exportierte Konstanten auf Package-Ebene trotzdem.
Lookup-Tabellen sind Variablen. Eine Map von Status zu Beschriftung kann keine Konstante sein. Deklarier sie also mit var auf Package-Ebene, oder gib sie aus einer Funktion zurück, wenn anderer Code sie nicht verändern soll:
example.gogovar orderStatusLabels = map[OrderStatus]string{ OrderStatusPending: "Awaiting payment", OrderStatusPaid: "Paid", OrderStatusShipped: "On the way", OrderStatusRefunded: "Refunded", }
Eine var auf Package-Ebene kann von jedem Code im Package geändert werden. Das ist der Preis dafür, dass sie jeden Typ aufnehmen kann. Sind die Werte fest und einfach, bekommst du mit einer Konstante Prüfungen zur Compile-Zeit und kein Risiko von Änderungen zur Laufzeit. Das Go-Keyword var erklärt Variablen, Nullwerte und := im Detail.
Wo LevelUpGo ins Spiel kommt
LevelUpGo bringt dir Go mit Übungen bei, die echten Go-Code im Browser ausführen. Go Basics führt const zusammen mit Variablen und Typen ein. Go Language Deep Dives hat eine Lektion zu iota und const-Mustern, in der du ein Enum, Bit-Flags und Byte-Größeneinheiten baust. Der Training Ground bietet kurze, eigenständige Übungen zum Üben außerhalb eines Kurses. Die anderen 24 reservierten Wörter findest du in Go Keywords: Alle 25 erklärt.
Häufig gestellte Fragen
Ist const ein Keyword in Go?
Ja. const ist eines der 25 reservierten Keywords von Go, du kannst es also nicht als Variablen-, Funktions- oder Typnamen verwenden. Es leitet eine Konstantendeklaration ein, entweder ein einzelnes const name = value oder einen gruppierten Block const ( ... ).
Kann ein Slice oder eine Map in Go eine Konstante sein?
Nein. Konstanten können nur boolesche Werte, Zahlen, Strings und Runes sein oder benannte Typen, die darauf aufbauen. const allowedMethods = []string{"GET", "POST"} scheitert mit is not constant. Verwende für Slices und Maps eine var auf Package-Ebene oder eine Funktion, die eine frische Kopie zurückgibt, wenn Aufrufer die gemeinsame Kopie nicht verändern dürfen.
Was ist iota in Go?
iota ist ein vordeklarierter Bezeichner, der die Zeilen eines const-Blocks nummeriert. In der ersten Zeile ist er 0, in jeder folgenden Zeile steigt er um eins, und in jedem neuen Block beginnt er von vorn. Mit einem benannten Typ und der impliziten Wiederholung gibt er jeder Konstante einer Liste ihren eigenen Wert, und so schreibt man in Go Enums und Bit-Flags.
Was ist der Unterschied zwischen typisierten und untypisierten Konstanten in Go?
Eine typisierte Konstante wie const maxRetries int32 = 3 hat einen festen Typ und folgt den normalen Zuweisungsregeln. Eine untypisierte Konstante wie const maxRetries = 3 hat keinen Typ, bis sie verwendet wird, und kann deshalb zu einem int, einem int64, einem float64 oder einer time.Duration werden. Untypisierte Konstanten behalten außerdem exakte Werte in beliebiger Genauigkeit, bis sie konvertiert werden.
Hat Go Enums?
Go hat kein Keyword enum, in der Praxis aber trotzdem Enums. Du deklarierst einen benannten Typ wie type OrderStatus int, listest seine Werte in einem const-Block mit iota auf und fügst eine Methode String() hinzu, die oft von stringer generiert wird. Der Compiler beschränkt den Typ nicht auf die aufgelisteten Werte. Validiere deshalb Eingaben und setze den Linter exhaustive ein, wenn jeder switch jeden Wert abdecken soll.
Quellen
- The Go Programming Language Specification, Constants: https://go.dev/ref/spec#Constants
- The Go Programming Language Specification, Constant declarations: https://go.dev/ref/spec#Constant_declarations
- The Go Programming Language Specification, Iota: https://go.dev/ref/spec#Iota
- The Go Programming Language Specification, Representability: https://go.dev/ref/spec#Representability
- The Go Blog, Constants (Rob Pike): https://go.dev/blog/constants
- Effective Go, Constants: https://go.dev/doc/effective_go#constants
- stringer: https://pkg.go.dev/golang.org/x/tools/cmd/stringer
- Go Code Review Comments, Mixed caps: https://go.dev/wiki/CodeReviewComments#mixed-caps
- Linter exhaustive: https://github.com/nishanths/exhaustive
