Zurück zum Blog

Go Keywords: Alle 25 reservierten Schlüsselwörter erklärt

Go hat 25 Keywords, unverändert seit Go 1.0. Was jedes davon macht, mit Praxisbeispiel, plus vordeklarierte Namen wie any und len, die keine Keywords sind.

Go Keywords: Alle 25 reservierten Schlüsselwörter erklärt

Go (Golang) hat 25 Keywords: break, case, chan, const, continue, default, defer, else, fallthrough, for, func, go, goto, if, import, interface, map, package, range, return, select, struct, switch, type und var. Sie sind reserviert, du kannst sie also nicht als Namen für Variablen, Funktionen oder Typen verwenden. Die Liste hat sich seit Go 1.0 im Jahr 2012 nicht verändert, und auch Go 1.27 hat noch dieselben 25 (Go spec). Python hat 35 Keywords, Java 51 und C++ mehr als 80. Die kurze Liste ist Absicht. Go wurde so klein entworfen, dass du die ganze Sprache im Kopf behalten kannst.

Kurzfassung

  • Go hat genau 25 Keywords. Die Liste steht in der Go-Spezifikation und ist seit Go 1.0 dieselbe.
  • Sie lassen sich in vier Gruppen einteilen. Deklarationen (package, import, const, var, type, func), zusammengesetzte Typen (struct, interface, map, chan), Kontrollfluss (12 Keywords von if bis return) sowie Nebenläufigkeit und Aufräumen (go, select, defer).
  • Es gibt nur ein Keyword für Schleifen. for deckt Zählschleifen, While-Schleifen, Endlosschleifen und, zusammen mit range, die Iteration über Slices, Maps, Channels, Integer und Iterator-Funktionen ab.
  • any, int, string, error, nil, true, len und make sind keine Keywords. Es sind vordeklarierte Bezeichner, du kannst sie also überschatten, auch wenn du das normalerweise nicht tun solltest.
  • Generics haben keine Keywords hinzugefügt. Go 1.18 hat Typparameter mit vordeklarierten Namen (any, comparable) eingeführt statt mit neuen reservierten Wörtern.

Alle Go-Keywords im Überblick

KeywordKategorieWas es macht
packageDeklarationLegt fest, zu welchem Package eine Datei gehört
importDeklarationBindet andere Packages ein
constDeklarationDeklariert eine Konstante, die zur Compile-Zeit feststeht
varDeklarationDeklariert eine Variable
typeDeklarationDeklariert einen benannten Typ oder einen Alias
funcDeklarationDeklariert eine Funktion, eine Methode oder ein Funktionsliteral
structZusammengesetzter TypFasst benannte Felder zu einem Typ zusammen
interfaceZusammengesetzter TypDeklariert eine Methodenmenge (oder einen Type Constraint)
mapZusammengesetzter TypDeklariert einen Hash-Map-Typ
chanZusammengesetzter TypDeklariert einen Channel-Typ
ifKontrollflussFührt einen Block aus, wenn eine Bedingung wahr ist
elseKontrollflussFührt einen Block aus, wenn die if-Bedingung falsch ist
forKontrollflussDie einzige Schleife in Go
rangeKontrollflussIteriert über Slices, Maps, Strings, Channels, Integer und Iteratoren
switchKontrollflussWählt einen von mehreren Zweigen aus
caseKontrollflussEin Zweig eines switch oder select
defaultKontrollflussDer Zweig, der läuft, wenn kein case passt
fallthroughKontrollflussMacht im nächsten case eines switch weiter
breakKontrollflussVerlässt ein for, switch oder select
continueKontrollflussSpringt zum nächsten Schleifendurchlauf
gotoKontrollflussSpringt zu einem Label in derselben Funktion
returnKontrollflussBeendet eine Funktion, optional mit Rückgabewerten
goNebenläufigkeitStartet eine Funktion in einer neuen Goroutine
selectNebenläufigkeitWartet auf mehrere Channel-Operationen
deferAufräumenFührt einen Aufruf aus, wenn die Funktion zurückkehrt

Welche Deklarations-Keywords gibt es in Go?

Go hat sechs Deklarations-Keywords, und dieser kleine Config-Loader verwendet alle sechs:

example.gogo
package main

import (
	"fmt"
	"time"
)

const defaultTimeout = 5 * time.Second

var maxRetries = 3

type Config struct {
	Addr    string
	Timeout time.Duration
}

func newConfig(addr string) Config {
	return Config{Addr: addr, Timeout: defaultTimeout}
}

func main() {
	cfg := newConfig(":8080")
	fmt.Println(cfg.Addr, cfg.Timeout, maxRetries)
}

package

Jede Go-Datei beginnt mit einer package-Klausel. Alle Dateien in einem Verzeichnis teilen sich denselben Package-Namen. Das Package namens main ist ein Sonderfall: Daraus wird ein ausführbares Programm, und seine main-Funktion ist der Einstiegspunkt. Jeder andere Name ergibt eine Library, die andere Packages importieren. Das Go-Keyword package erklärt Namensregeln, Sichtbarkeit und _test-Packages im Detail.

import

import macht die exportierten Namen eines anderen Packages in der Datei verfügbar, etwa fmt.Println oder http.ListenAndServe. Du kannst Imports in Klammern gruppieren, einen Import umbenennen (yaml "gopkg.in/yaml.v3") oder ein Package mit _ nur wegen seiner Seiteneffekte importieren. Go weigert sich, eine Datei mit einem ungenutzten Import zu kompilieren. Eine Datei listet also nie ein Package, das sie nicht verwendet. Das Go-Keyword import erklärt Aliase, Blank- und Dot-Imports sowie Import-Zyklen im Detail.

const

const deklariert einen Wert, der zur Compile-Zeit feststeht, zum Beispiel ein Timeout oder einen Header-Namen. Konstanten können untypisiert sein. const maxBodyBytes = 1 << 20 hat keinen Typ, bis du die Konstante verwendest, daher funktioniert sie ohne Konvertierung als int oder als int64. Innerhalb eines const-Blocks zählt der vordeklarierte Bezeichner iota ab 0 hoch. So schreibt man in Go Enums. Das Go-Keyword const erklärt typisierte und untypisierte Konstanten, iota und Enums im Detail.

var

var deklariert eine Variable mit einem expliziten Typ, einem Anfangswert oder beidem. Jede Variable startet mit dem Nullwert ihres Typs: 0, "", false oder nil. Innerhalb von Funktionen verwendet der meiste Go-Code stattdessen die Kurzform name := value. Auf Package-Ebene ist var trotzdem die richtige Wahl, ebenso wenn du bewusst den Nullwert willst, wie bei var buf bytes.Buffer. Das Go-Keyword var erklärt Nullwerte, :=, Shadowing und Variablen auf Package-Ebene im Detail.

type

type deklariert einen neuen benannten Typ, etwa ein Struct, ein Interface oder eine benannte Variante eines bestehenden Typs wie type UserID int64. Der neue Typ kann eigene Methoden haben. Mit = deklariert es stattdessen einen Alias (type Handler = http.Handler), also nur einen zweiten Namen für denselben Typ. Das Go-Keyword type erklärt definierte Typen, Aliase, Methoden und generische Typen im Detail.

func

func deklariert Funktionen und Methoden und erzeugt außerdem Funktionsliterale (Closures). Eine Methode hat vor ihrem Namen einen Receiver: func (s *Server) Start() error. Go-Funktionen können mehrere Werte zurückgeben, und genau so werden Fehler gemeldet: func parse(body []byte) (Request, error). Das Go-Keyword func erklärt Methoden, Closures, variadische Funktionen und Functional Options im Detail.

Welche Go-Keywords bilden zusammengesetzte Typen?

Vier Keywords bauen Typen aus anderen Typen. Arrays, Slices und Pointer sind ebenfalls zusammengesetzte Typen, verwenden aber statt Keywords Symbole ([N]T, []T, *T).

example.gogo
type Notifier interface {
	Notify(ctx context.Context, msg string) error
}

type SlackNotifier struct {
	Channel string
}

func (s SlackNotifier) Notify(ctx context.Context, msg string) error {
	fmt.Printf("#%s: %s\n", s.Channel, msg)
	return nil
}

func main() {
	var n Notifier = SlackNotifier{Channel: "alerts"}

	statusCounts := map[int]int{200: 41, 500: 2}
	events := make(chan string, 10)

	events <- fmt.Sprintf("%d server errors", statusCounts[500])
	n.Notify(context.Background(), <-events)
}

struct

struct fasst benannte Felder zu einem Typ zusammen. In Go ersetzt es Klassen: Du hängst Methoden an einen Struct-Typ, und du verwendest Verhalten wieder, indem du ein Struct in ein anderes einbettest, statt davon zu erben. Struct-Tags wie json:"email" sagen Packages wie encoding/json, wie jedes Feld kodiert werden soll. Das Go-Keyword struct erklärt Struct-Tags, Value- und Pointer-Receiver, Embedding sowie das Vergleichen und Kopieren von Structs im Detail.

interface

interface deklariert eine Menge von Methoden. Ein Typ erfüllt ein Interface allein dadurch, dass er diese Methoden hat, ganz ohne implements-Deklaration. SlackNotifier oben ist also ein Notifier, ohne es anzugeben. Seit Go 1.18 beschreiben Interfaces außerdem Type Constraints für Generics, zum Beispiel interface{ ~int | ~float64 }. Das Go-Keyword interface erklärt implizite Erfüllung, nil-Interfaces, Type Assertions und Constraints im Detail.

map

map deklariert einen Hash-Map-Typ, geschrieben als map[KeyType]ValueType. Du erzeugst eine Map mit einem Literal oder mit make, und eine nil-Map löst beim Schreiben eine Panic aus. Das Lesen eines fehlenden Keys liefert den Nullwert, und die Form mit zwei Rückgabewerten, count, ok := statusCounts[404], verrät dir, ob der Key vorhanden war. Die Spezifikation legt die Iterationsreihenfolge nicht fest, und die Runtime randomisiert sie absichtlich. var vs make in Go erklärt, warum eine nil-Map beim Schreiben eine Panic auslöst, ein nil-Slice aber nicht. Das Go-Keyword map erklärt das Comma-ok-Idiom, die Iterationsreihenfolge, Sets, nebenläufigen Zugriff und das Paket maps im Detail.

chan

chan deklariert einen Channel-Typ, also die Leitung, über die Goroutines sich gegenseitig Werte übergeben. chan<- Job kann nur senden und <-chan Job nur empfangen. So kann eine Funktionssignatur angeben, welche Richtung sie nutzt. Channels werden mit make erzeugt, und ein gepufferter Channel (make(chan string, 10)) hält Werte vor, bis jemand sie empfängt. Das Go-Keyword chan erklärt gepufferte und ungepufferte Channels, das Schließen, Deadlocks und select-Muster im Detail.

Welche Go-Keywords steuern den Programmablauf?

Zwölf Keywords kümmern sich um Verzweigungen und Schleifen. Go verzichtet auf while, do, try, catch und den ternären Operator. Fehler sind ganz normale Rückgabewerte, die du mit if prüfst.

example.gogo
func parse(body []byte) (Request, error) {
	var req Request
	if err := json.Unmarshal(body, &req); err != nil {
		return Request{}, fmt.Errorf("decode request: %w", err)
	}
	if req.Email == "" {
		return Request{}, errors.New("email is required")
	}
	return req, nil
}

func countErrors(lines []string) int {
	errs := 0
	for _, line := range lines {
		if strings.HasPrefix(line, "#") {
			continue
		}
		if line == "EOF" {
			break
		}
		if strings.Contains(line, "level=error") {
			errs++
		}
	}
	return errs
}

if

if führt einen Block aus, wenn eine Bedingung wahr ist. Die Bedingung braucht keine Klammern, die geschweiften Klammern sind aber Pflicht. Ein if kann mit einer kurzen Anweisung beginnen, und die Variable, die sie deklariert, existiert nur innerhalb des if und seiner else-Zweige. Go-Code nutzt diese Form ständig, etwa in if err := json.Unmarshal(body, &req); err != nil. Das Go-Keyword if erklärt reine bool-Bedingungen, Init-Statements, Shadowing, Early Returns und den fehlenden ternären Operator im Detail.

else

else läuft, wenn die if-Bedingung falsch ist, und else if hängt weitere Bedingungen an. Idiomatisches Go verwendet else seltener als die meisten Sprachen. Wenn der if-Zweig mit return endet, ist der Rest der Funktion schon der Else-Fall. Deshalb hält Go-Code den Happy Path ohne Einrückung und kehrt bei Fehlern früh zurück. Das Go-Keyword else erklärt else if-Ketten, den Scope von Variablen aus dem if, frühe Returns und den fehlenden ternären Operator im Detail.

for

for ist das einzige Schleifen-Keyword in Go. for i := 0; i < n; i++ ist die klassische Zählschleife, for queue.Len() > 0 ist eine While-Schleife und for { ... } läuft endlos, bis etwas sie mit break verlässt. Seit Go 1.22 bekommt in Modulen, deren go.mod go 1.22 oder neuer angibt, jeder Durchlauf eine eigene Kopie der Schleifenvariable. Closures und Goroutines, die in einer Schleife gestartet werden, teilen sich also keine gemeinsame Variable mehr. Das Go-Keyword for erklärt die drei Schleifenformen, range, die Schleifenvariablen seit Go 1.22, Labels und typische Fehler in Schleifen im Detail.

range

range lässt eine for-Schleife über eine Collection iterieren. Du bekommst Index und Wert bei Slices und Arrays, Key und Wert bei Maps, Byte-Offset und Rune bei Strings und die empfangenen Werte bei Channels. Go 1.22 hat die Iteration über einen Integer ergänzt (for attempt := range 3 läuft mit 0, 1 und 2), und Go 1.23 die Iteration über Iterator-Funktionen wie die, die maps.Keys(m) zurückgibt.

switch

switch wählt einen von mehreren Zweigen aus. Anders als in C und Java hört ein switch in Go nach dem passenden Case auf, du brauchst also nie ein break am Ende jedes Case. Ein switch ohne Ausdruck funktioniert wie eine aufgeräumtere if/else if-Kette, und ein Type Switch (switch v := x.(type)) verzweigt nach dem dynamischen Typ eines Interface-Werts.

case

case markiert einen Zweig eines switch oder eines select. Ein einzelner Case kann mehrere Werte auflisten: case "GET", "HEAD":. In einem switch werden die Cases von oben nach unten geprüft, und der erste Treffer gewinnt.

default

default ist der Zweig, der läuft, wenn kein case passt. Er kann an beliebiger Stelle im switch stehen, per Konvention kommt er aber zuletzt. In einem select macht ein default-Case die ganze Anweisung nicht-blockierend: Ist kein Channel bereit, läuft default sofort.

fallthrough

fallthrough lässt einen switch in den Body des nächsten Case weiterlaufen, ohne dessen Bedingung zu prüfen. In echtem Code ist das selten, und es ist die einzige Stelle, an der sich ein switch in Go wie in C verhält. Gestaffelte Berechtigungen sind ein Fall, in dem es sich gut liest:

example.gogo
func permissions(role string) []string {
	var perms []string
	switch role {
	case "admin":
		perms = append(perms, "delete")
		fallthrough
	case "editor":
		perms = append(perms, "write")
		fallthrough
	case "viewer":
		perms = append(perms, "read")
	default:
		return nil
	}
	return perms
}

permissions("editor") liefert [write read] und permissions("admin") liefert [delete write read]. fallthrough muss die letzte Anweisung in einem Case sein und ist in einem Type Switch nicht erlaubt.

break

break verlässt das innerste for, switch oder select. Der letzte Teil ist eine häufige Falle: Ein break innerhalb eines select verlässt nur das select, nicht die Schleife drumherum. Um die Schleife zu verlassen, gibst du ihr ein Label und schreibst break loop:

example.gogo
func drain(ctx context.Context, events <-chan string) {
loop:
	for {
		select {
		case ev, ok := <-events:
			if !ok {
				break loop
			}
			fmt.Println("event:", ev)
		case <-ctx.Done():
			break loop
		}
	}
	fmt.Println("drained")
}

continue

continue überspringt den Rest des aktuellen Durchlaufs und startet den nächsten. In countErrors oben überspringt es Kommentarzeilen. Wie break kann es ein Label bekommen, um aus einer verschachtelten Schleife heraus mit der äußeren weiterzumachen.

goto

goto springt zu einem Label in derselben Funktion. Es kann nicht in einen Block hinein und nicht über eine Variablendeklaration hinweg springen, und das schließt die meisten Probleme aus, die goto in C hat. Du wirst es selten schreiben. Es taucht vor allem in generiertem Code und in ein paar Low-Level-Teilen der Standardbibliothek auf. Das Backend von LevelUpGo selbst hat ohne Tests und Seed-Daten etwa 30.000 Zeilen Go. Es verwendet goto und fallthrough kein einziges Mal, dagegen mehr als 250 range-Schleifen und mehr als 40 defer-Aufrufe.

return

return beendet eine Funktion und gibt ihre Ergebnisse zurück. Go-Funktionen geben oft zwei Werte zurück, ein Ergebnis und einen error, und der Aufrufer prüft den Fehler sofort. Mit benannten Rückgabeparametern gibt ein nacktes return deren aktuelle Werte zurück, die meisten Style Guides beschränken das aber auf kurze Funktionen.

Welche Go-Keywords sind für Nebenläufigkeit und Aufräumen zuständig?

Die letzten drei Keywords sind go, select und defer. Zusammen mit chan sind sie das, was ein Go-Server verwendet, um Arbeit parallel auszuführen und danach aufzuräumen.

example.gogo
func startWorkers(ctx context.Context, n int, jobs <-chan string, results chan<- []byte) {
	for range n {
		go worker(ctx, jobs, results)
	}
}

func worker(ctx context.Context, jobs <-chan string, results chan<- []byte) {
	for {
		select {
		case url, ok := <-jobs:
			if !ok {
				return
			}
			body, err := fetch(ctx, url)
			if err != nil {
				log.Printf("fetch %s: %v", url, err)
				continue
			}
			results <- body
		case <-ctx.Done():
			return
		}
	}
}

func fetch(ctx context.Context, url string) ([]byte, error) {
	ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
	defer cancel()

	req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
	if err != nil {
		return nil, err
	}
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	return io.ReadAll(resp.Body)
}

go

go startet einen Funktionsaufruf in einer neuen Goroutine und macht weiter, ohne darauf zu warten. startWorkers oben führt go worker(ctx, jobs, results) in einer Schleife aus, und so entsteht ein Worker-Pool. Goroutines starten mit wenigen Kilobyte Stack, daher kann ein Server Hunderttausende davon laufen lassen. Die go-Anweisung verwirft die Rückgabewerte der Funktion. Ergebnisse kommen deshalb über Channels zurück, und eine sync.WaitGroup oder eine errgroup wartet, bis die Arbeit erledigt ist.

select

select wartet auf mehrere Channel-Operationen und führt die erste aus, die bereit ist. Sind mehrere gleichzeitig bereit, wählt es zufällig eine davon aus. Der Worker oben nutzt es, um den nächsten Job zu holen oder aufzuhören, wenn der Context abgebrochen wird. Einen Channel mit ctx.Done() oder time.After zu kombinieren ist der Weg, auf dem Go-Code allem, was blockiert, Timeouts und Abbruch hinzufügt.

defer

defer plant einen Aufruf ein, der läuft, wenn die umgebende Funktion zurückkehrt, egal ob sie normal endet oder eine Panic auslöst. So bleibt das Aufräumen direkt neben dem Code, der es braucht: defer resp.Body.Close(), defer mu.Unlock(), defer cancel(). Aufgeschobene Aufrufe laufen in LIFO-Reihenfolge (last in, first out), und ihre Argumente werden ausgewertet, wenn die defer-Zeile läuft, nicht erst am Ende. Ein defer in einer Schleife läuft erst, wenn die ganze Funktion zurückkehrt. Das ist einer der häufigen Fehler in Go, die du früh kennen solltest.

Was ist in Go kein Keyword?

Viele Namen, die eingebaut aussehen, sind vordeklarierte Bezeichner und keine Keywords. Die Go-Spezifikation führt sie im Universe Block auf, der jedes Package umschließt (Go spec):

ArtVordeklarierte Bezeichner
Typenany, bool, byte, comparable, complex64, complex128, error, float32, float64, int, int8, int16, int32, int64, rune, string, uint, uint8, uint16, uint32, uint64, uintptr
Konstantentrue, false, iota
Nullwertnil
Funktionenappend, cap, clear, close, complex, copy, delete, imag, len, make, max, min, new, panic, print, println, real, recover

Der Unterschied ist wichtig, weil vordeklarierte Bezeichner in einem inneren Scope neu deklariert werden können. Das hier kompiliert:

example.gogo
func logAddrChange(cur, next Config) {
	old, new := cur.Addr, next.Addr
	if old != new {
		log.Printf("addr changed from %s to %s", old, new)
	}
}

func countLines(lines []string) int {
	len := len(lines)
	return len
}

Eine Variable new zu nennen ist in Code, der alte und neue Werte vergleicht, üblich, und es funktioniert. Nach len := len(lines) schlägt aber jeder spätere Aufruf von len(...) in dieser Funktion mit invalid operation: cannot call len (variable of type int): int is not a function fehl. Ein Keyword lässt sich so überhaupt nicht verwenden. var := 3 scheitert mit syntax error: unexpected :=, expected name. Linter wie predeclared in golangci-lint markieren überschattete Built-ins, weil Code wie countLines zwar kompiliert, aber den nächsten Leser verwirrt.

Weil diese Namen nicht auf der Keyword-Liste stehen, kann Go wachsen, ohne bestehende Programme kaputt zu machen. any (Go 1.18) sowie min, max und clear (Go 1.21) kamen als vordeklarierte Bezeichner dazu. Code, der diese Namen schon als Variablen verwendet hat, ließ sich also weiterhin kompilieren.

Wo LevelUpGo ins Spiel kommt

LevelUpGo bringt dir Go mit Übungen bei, die echten Go-Code im Browser ausführen. Der Kurs Go Basics behandelt die Deklarations- und Kontrollfluss-Keywords, von var und const bis switch und for. Concurrency Fundamentals behandelt go, chan, select und defer, und Simplification zeigt, wann sich ein switch besser liest als eine if-Kette. Der Training Ground bietet kurze, eigenständige Übungen, mit denen du sie außerhalb eines Kurses trainieren kannst.

Häufig gestellte Fragen

Wie viele Keywords hat Go?

Go hat 25 Keywords: break, case, chan, const, continue, default, defer, else, fallthrough, for, func, go, goto, if, import, interface, map, package, range, return, select, struct, switch, type und var. Die Liste ist in der Go-Spezifikation festgelegt und hat sich seit Go 1.0 nicht verändert.

Ist any ein Keyword in Go?

Nein. any ist ein vordeklarierter Bezeichner, ein Alias für interface{}, der mit Go 1.18 dazukam. Dasselbe gilt für int, string, error, nil, true, false, iota und eingebaute Funktionen wie len, make und append. Du kannst sie in einem inneren Scope neu deklarieren, Linter warnen dann allerdings.

Kann ich ein Go-Keyword als Variablennamen verwenden?

Nein. Keywords sind reserviert, daher sind var := 3 oder func type() Syntaxfehler. Der übliche Ausweg ist eine kurze Abwandlung: Go-Code verwendet oft typ oder kind statt type, pkg statt package und fn statt func.

Hat Go seit Go 1.0 neue Keywords bekommen?

Nein. Die 25 Keywords in Go 1.27 sind dieselben 25, die im März 2012 mit Go 1.0 ausgeliefert wurden. Generics, range über Integer, Iterator-Funktionen und die generischen Methoden in Go 1.27 kamen alle ohne neue Keywords aus. Das Go-1-Kompatibilitätsversprechen macht neue Keywords unwahrscheinlich, denn ein neues reserviertes Wort würde jedes Programm kaputt machen, das es schon als Namen verwendet.

Warum hat Go so wenige Keywords?

Die Designer wollten es so. In der Go FAQ steht, sie hätten „versucht, Ballast und Komplexität zu reduzieren“, und die „Syntax von Go ist sauber und kommt mit wenigen Keywords aus“. Deshalb hat Go ein Schleifen-Keyword statt drei, und deshalb sind Fehler Rückgabewerte statt try/catch. Das Ergebnis ist eine Sprache, die man schnell lernt, und Go-Code aus verschiedenen Codebases liest sich sehr ähnlich.

Quellen

Schreib Go wie ein Senior Engineer

Interaktive Lektionen im Browser. Die ersten sind kostenlos.

Kostenlose Lektion testenOder kostenloses Konto erstellen