Go ma dwa sposoby deklarowania zmiennej: var, który działa wszędzie, i :=, krótką formę, która działa wewnątrz funkcji i wnioskuje typ.
Każda zmienna ma stały typ ustalony podczas kompilacji. Gdy podajesz wartość, Go wnioskuje z niej typ, więc dopisanie typu (var city string = "Lisbon") jest poprawne, ale zbędne.
var a :=
var | := | |
|---|---|---|
| Gdzie | na poziomie pakietu i wewnątrz funkcji | tylko wewnątrz funkcji |
| Typ | opcjonalny; wymagany, gdy nie ma wartości | zawsze wnioskowany |
| Wartość początkowa | opcjonalna (w przeciwnym razie wartość zerowa) | wymagana |
| Ponowna deklaracja | nigdy | dozwolona, jeśli co najmniej jedna zmienna po lewej jest nowa |
Idiomatyczny wybór wewnątrz funkcji:
x := value, gdy masz wartość początkową.var x T, gdy chcesz wartość zerową, co sygnalizuje „celowo puste”:var buf bytes.Buffer,var total int.var x T = value, gdy wywnioskowany typ nie jest tym, którego chcesz, na przykładvar ratio float64 = 1(samoratio := 1dałobyint).
:= poza funkcją to błąd składni:
./main.go:5:1: syntax error: non-declaration statement outside function body
Wartości zerowe
Zmienna zadeklarowana bez wartości nigdy nie zawiera śmieci. Przechowuje wartość zerową swojego typu:
| Typ | Wartość zerowa |
|---|---|
| liczby całkowite, zmiennoprzecinkowe | 0 |
bool | false |
string | "" |
| wskaźniki, slice'y, mapy, kanały, funkcje, interfejsy | nil |
| tablice, struktury | każdy element lub pole ustawione na wartość zerową |
Dobre API w Go są projektowane tak, by wartość zerowa była użyteczna: zerowy sync.Mutex jest odblokowany, zerowy bytes.Buffer jest pusty i gotowy, a len, range i append działają na slice'ie nil. Uważaj na mapy: odczyt z mapy nil zwraca wartości zerowe, ale zapis do niej wywołuje panic. Twórz mapy przez make lub literał, zanim coś w nich zapiszesz.
Deklarowanie kilku zmiennych
Blok var ( ... ) grupuje powiązane zmienne na poziomie pakietu. W przypisaniu wielokrotnym, takim jak x, y = y, x, każde wyrażenie po prawej jest obliczane przed jakimkolwiek przypisaniem, dlatego zamiana działa.
:= z kilkoma nazwami niczego nie deklaruje ponownie, dopóki co najmniej jedna nazwa po lewej jest nowa. Istniejące nazwy w tym samym zasięgu są po prostu przypisywane. Dzięki temu możesz ponownie używać err:
f, err := os.Open("a.txt") // declares f and err
g, err := os.Open("b.txt") // declares g, assigns to the existing err
Jeśli żadna z nazw nie jest nowa, to błąd:
./main.go:7:4: no new variables on left side of :=
Użyj zwykłego =, aby przypisać wartość do zmiennej, która już istnieje.
Nieużywane zmienne to błędy
Zmienna lokalna, która została zadeklarowana, ale nigdy nie została odczytana, zatrzymuje kompilację:
./main.go:9:2: declared and not used: x
To nie jest ostrzeżenie, które można wyłączyć. Usuń zmienną, użyj jej albo podczas debugowania wycisz ją pustym identyfikatorem: _ = x. Pusty identyfikator odrzuca też niepotrzebne wartości zwracane: _, err := fmt.Println("hi").
Zmienne na poziomie pakietu i parametry funkcji są z tej zasady zwolnione.
Zasięg i przesłanianie
Zmienna istnieje od deklaracji do końca otaczającego bloku ({ ... }). := w bloku wewnętrznym tworzy nową zmienną, nawet jeśli na zewnątrz istnieje zmienna o tej samej nazwie. Zewnętrzna zostaje przesłonięta, a nie zmieniona:
Program wypisuje inside: 42, a potem outside: 0. := wewnątrz if zadeklarowało zarówno count, jak i err jako nowe zmienne w bloku wewnętrznym, bo żadna z nich jeszcze tam nie istniała. Kod kompiluje się bez problemu i jest klasycznym źródłem błędów typu „moja wartość zniknęła”.
Rozwiązaniem jest najpierw zadeklarować err i przypisywać przez =:
var err error
count, err = strconv.Atoi(input)
go vet domyślnie nie zgłasza przesłaniania. Robi to osobny analizator shadow, a lintery takie jak golangci-lint mogą go włączyć.
Nazewnictwo
- Używaj
camelCase:maxRetries,userID. Niemax_retries. - Akronimy zapisuj jednolicie:
userID,httpClient,parseURL, a nieuserId. - Krótkie nazwy dla krótkich zasięgów:
iw pętli,rdla readera,errdla błędu. Dłuższe, opisowe nazwy dla zmiennych na poziomie pakietu. - Nazwa z wielkiej litery na poziomie pakietu (
var Timeout) jest eksportowana do innych pakietów. Zobacz stronę o pakietach i importach.
Zmienne na poziomie pakietu
Zmienne zadeklarowane poza funkcjami żyją przez cały czas działania programu i są inicjalizowane przed uruchomieniem main, w kolejności zależności. Są wygodne do konfiguracji i cache, a współdzieli je każda goroutine. Jeśli do jednej z nich zapisuje więcej niż jedna goroutine, chroń ją mutexem albo użyj sync/atomic, bo inaczej masz wyścig danych.
Dla wartości, które nigdy się nie zmieniają, wybieraj stałe. const nie da się przypadkiem zmodyfikować i nic nie kosztuje w czasie działania.
Najczęściej zadawane pytania
Czym różni się var od := w Go?
:= deklaruje i inicjalizuje zmienną z wywnioskowanym typem i działa tylko wewnątrz funkcji: count := 10. var działa wszędzie (także na poziomie pakietu), pozwala podać typ i może pominąć wartość początkową, żeby dostać wartość zerową: var count int. Wewnątrz funkcji idiomatyczne Go używa :=, gdy jest wartość początkowa, i var, gdy chcesz wartość zerową.
Czym są wartości zerowe w Go?
Każda zmienna zadeklarowana bez wartości dostaje wartość zerową swojego typu: 0 dla liczb, false dla bool, "" dla string i nil dla wskaźników, slice'ów, map, kanałów, funkcji i interfejsów. Struktury dostają wartość zerową każdego pola. Go nie ma niezainicjalizowanych zmiennych.
Jak naprawić błąd "declared and not used" w Go?
Go odrzuca zmienną lokalną, do której coś przypisano, ale której nigdy nie odczytano. Użyj jej, usuń ją albo podczas debugowania przypisz ją do pustego identyfikatora przez _ = x. Zmienne na poziomie pakietu i parametry funkcji są z tej zasady zwolnione.
Jak zadeklarować zmienną globalną w Go?
Użyj var na poziomie pakietu, poza jakąkolwiek funkcją: var counter int. Krótka forma := jest tam niedozwolona. Nazwa z wielkiej litery (var Counter int) jest widoczna dla innych pakietów; z małej litery tylko we własnym pakiecie. Chroń zmienne na poziomie pakietu mutexem, jeśli zapisują do nich goroutine.