Go hat zwei Arten, eine Variable zu deklarieren: var, das überall funktioniert, und :=, eine Kurzform, die innerhalb von Funktionen funktioniert und den Typ ableitet.
Jede Variable hat einen festen Typ, der zur Compilezeit feststeht. Gibst du einen Wert an, leitet Go den Typ daraus ab. Den Typ zusätzlich hinzuschreiben (var city string = "Lisbon") ist erlaubt, aber überflüssig.
var vs :=
var | := | |
|---|---|---|
| Wo | auf Paketebene und in Funktionen | nur in Funktionen |
| Typ | optional; Pflicht, wenn es keinen Wert gibt | immer abgeleitet |
| Startwert | optional (sonst Nullwert) | Pflicht |
| Neu deklarieren | nie | erlaubt, wenn mindestens eine Variable links neu ist |
Die idiomatische Wahl innerhalb einer Funktion:
x := value, wenn du einen Startwert hast.var x T, wenn du den Nullwert willst. Das signalisiert „absichtlich leer“:var buf bytes.Buffer,var total int.var x T = value, wenn der abgeleitete Typ nicht der gewünschte ist, zum Beispielvar ratio float64 = 1(ein schlichtesratio := 1wäre einint).
:= außerhalb einer Funktion ist ein Syntaxfehler:
./main.go:5:1: syntax error: non-declaration statement outside function body
Nullwerte
Eine Variable, die ohne Wert deklariert wird, enthält nie Datenmüll. Sie hat den Nullwert ihres Typs:
| Typ | Nullwert |
|---|---|
| Ganzzahlen, Floats | 0 |
bool | false |
string | "" |
| Pointer, Slices, Maps, Channels, Funktionen, Interfaces | nil |
| Arrays, Structs | jedes Element oder Feld auf seinen Nullwert gesetzt |
Gute Go-APIs sind so entworfen, dass der Nullwert brauchbar ist: Ein sync.Mutex mit Nullwert ist entsperrt, ein bytes.Buffer mit Nullwert ist leer und einsatzbereit, und len, range und append funktionieren alle auf einem nil-Slice. Aufpassen musst du bei der Map: Aus einer nil-Map zu lesen liefert Nullwerte, in eine zu schreiben löst eine Panic aus. Erzeuge Maps mit make oder einem Literal, bevor du etwas hineinschreibst.
Mehrere Variablen deklarieren
Der Block var ( ... ) gruppiert zusammengehörige Variablen auf Paketebene. Bei einer Mehrfachzuweisung wie x, y = y, x wird jeder Ausdruck rechts ausgewertet, bevor irgendetwas zugewiesen wird. Deshalb funktioniert das Tauschen.
:= mit mehreren Namen ist erlaubt, solange mindestens ein Name links neu ist. Vorhandene Namen im selben Scope werden einfach zugewiesen. Deshalb kannst du err wiederverwenden:
f, err := os.Open("a.txt") // declares f and err
g, err := os.Open("b.txt") // declares g, assigns to the existing err
Ist keiner der Namen neu, gibt es einen Fehler:
./main.go:7:4: no new variables on left side of :=
Nimm ein schlichtes =, um einer bereits existierenden Variablen etwas zuzuweisen.
Ungenutzte Variablen sind Fehler
Eine lokale Variable, die deklariert, aber nie gelesen wird, stoppt den Build:
./main.go:9:2: declared and not used: x
Das ist keine Warnung, die du abschalten kannst. Lösch die Variable, benutz sie oder bring sie beim Debuggen mit dem Blank Identifier zum Schweigen: _ = x. Der Blank Identifier verwirft auch Rückgabewerte, die du nicht brauchst: _, err := fmt.Println("hi").
Variablen auf Paketebene und Funktionsparameter sind von dieser Regel ausgenommen.
Scope und Shadowing
Eine Variable existiert von ihrer Deklaration bis zum Ende des umschließenden Blocks ({ ... }). Ein := in einem inneren Block erzeugt eine neue Variable, auch wenn außen eine mit demselben Namen existiert. Die äußere wird verschattet, nicht geändert:
Das Programm gibt inside: 42 und dann outside: 0 aus. Das := im if hat sowohl count als auch err als neue Variablen im inneren Block deklariert, weil dort noch keine davon existierte. Es kompiliert sauber und ist eine klassische Quelle für Bugs der Sorte „mein Wert ist verschwunden“.
Die Lösung: err vorher deklarieren und mit = zuweisen:
var err error
count, err = strconv.Atoi(input)
go vet meldet Shadowing standardmäßig nicht. Der separate shadow-Analyzer tut es, und Linter wie golangci-lint können ihn aktivieren.
Benennung
- Nimm
camelCase:maxRetries,userID. Nichtmax_retries. - Schreib Akronyme einheitlich:
userID,httpClient,parseURL, nichtuserId. - Kurze Namen für kurze Scopes:
iin einer Schleife,rfür einen Reader,errfür einen Fehler. Längere, beschreibende Namen für Variablen auf Paketebene. - Ein großgeschriebener Name auf Paketebene (
var Timeout) wird an andere Pakete exportiert. Siehe Pakete und Imports.
Variablen auf Paketebene
Außerhalb von Funktionen deklarierte Variablen leben während des ganzen Programms und werden vor dem Start von main in der Reihenfolge ihrer Abhängigkeiten initialisiert. Sie sind praktisch für Konfiguration und Caches, und alle Goroutinen teilen sie sich. Schreibt mehr als eine Goroutine darauf, schütze sie mit einem Mutex oder nimm sync/atomic, sonst hast du ein Data Race.
Für Werte, die sich nie ändern, nimm lieber Konstanten. Eine const kann nicht versehentlich geändert werden und kostet zur Laufzeit nichts.
Häufig gestellte Fragen
Was ist der Unterschied zwischen var und := in Go?
:= deklariert und initialisiert eine Variable mit abgeleitetem Typ und funktioniert nur innerhalb von Funktionen: count := 10. var funktioniert überall (auch auf Paketebene), erlaubt die Angabe des Typs und kann den Startwert weglassen, dann gilt der Nullwert: var count int. Innerhalb von Funktionen nutzt idiomatisches Go :=, wenn es einen Startwert gibt, und var, wenn du den Nullwert willst.
Was sind Nullwerte in Go?
Jede Variable, die ohne Wert deklariert wird, bekommt den Nullwert ihres Typs: 0 für Zahlen, false für bool, "" für string und nil für Pointer, Slices, Maps, Channels, Funktionen und Interfaces. Structs bekommen für jedes Feld dessen Nullwert. Nicht initialisierte Variablen gibt es in Go nicht.
Wie behebe ich „declared and not used“ in Go?
Go lehnt eine lokale Variable ab, die zugewiesen, aber nie gelesen wird. Benutz sie, lösch sie oder weise sie beim Debuggen mit _ = x dem Blank Identifier zu. Variablen auf Paketebene und Funktionsparameter sind ausgenommen.
Wie deklariere ich eine globale Variable in Go?
Mit var auf Paketebene, außerhalb jeder Funktion: var counter int. Die Kurzform := ist dort nicht erlaubt. Ein großgeschriebener Name (var Counter int) ist für andere Pakete sichtbar, ein kleingeschriebener nur im eigenen Paket. Schütze Variablen auf Paketebene mit einem Mutex, wenn Goroutinen sie schreiben.