Stała to wartość ustalona w czasie kompilacji. Deklarujesz ją przez const, a Go odrzuca każdą próbę jej zmiany.
Stałe można deklarować na poziomie pakietu lub wewnątrz funkcji, po jednej w linii albo zgrupowane w bloku const ( ... ). MB i GB są obliczane z innych stałych, a kompilator wykonuje te obliczenia raz, podczas budowania.
Przypisanie do stałej, na przykład MaxRetries = 5, to błąd kompilacji:
./main.go:8:2: cannot assign to MaxRetries (neither addressable nor a map index expression)
Co może być stałą
Tylko trzy rodzaje wartości: wartości logiczne, liczby (całkowite, zmiennoprzecinkowe, zespolone i runy) oraz stringi. Wartość musi dać się obliczyć przez kompilator, czyli mogą to być literały, inne stałe, działania na nich i kilka funkcji wbudowanych, jak len ze stałego stringa.
Wszystko, co wymaga uruchomienia kodu lub alokacji pamięci, odpada:
const Colors = []string{"red", "green"} // slice
const Started = time.Now() // function call
./main.go:9:16: []string{…} (value of type []string) is not constant
./main.go:10:13: time.Now() (value of struct type time.Time) is not constant
Dlaczego Go nie ma stałych slice'ów ani map
Slice'y, mapy i struktury, które je zawierają, to referencje do pamięci istniejącej w czasie działania, więc kompilator nie może wbudować ich w program jako stałych wartości, a Go nie ma modyfikatora readonly ani final dla zmiennych. Typowe obejścia:
Kopia kosztuje jedną alokację na wywołanie, co jest w porządku przy danych wielkości konfiguracji. Do wyszukiwania po kluczu inną opcją przypominającą stałą jest switch w funkcji: func statusText(code int) string { switch code { ... } }.
Stałe beztypowe
Stała zadeklarowana bez typu jest beztypowa (untyped). Ma rodzaj (liczba całkowita, zmiennoprzecinkowa, runa, string, bool), ale nie ma konkretnego typu Go, dopóki nie zostanie użyta, i dopasowuje się do kontekstu:
Ratio działa jako int, float64, uint8 i float32 bez konwersji. Dlatego time.Sleep(2 * time.Second) się kompiluje: 2 jest beztypowe i staje się time.Duration.
Gdy kontekst nie wymaga typu, jak w x := Ratio, stała dostaje swój typ domyślny:
| Rodzaj stałej beztypowej | Typ domyślny |
|---|---|
całkowita (42) | int |
zmiennoprzecinkowa (4.2) | float64 |
runa ('a') | rune (int32) |
zespolona (2i) | complex128 |
| string | string |
| logiczna | bool |
Dokładna arytmetyka
Beztypowe stałe liczbowe są dokładne. Kompilator reprezentuje liczby całkowite na co najmniej 256 bitach, więc wartości pośrednie mogą być dużo większe niż jakikolwiek typ Go:
Samej Huge nie da się wypisać jako liczby całkowitej, bo przekazanie jej do fmt.Println konwertuje ją na int, a kompilator to wyłapuje (float64(Huge) działa, bo float pomieści taką wielkość):
cannot use Huge (untyped int constant 1267650600228229401496703205376) as int value in argument to fmt.Println (overflows)
Ta sama kontrola zatrzymuje w czasie kompilacji mniejsze pomyłki, jak var b byte = 300:
cannot use 300 (untyped int constant) as byte value in variable declaration (overflows)
Stałe typowane
Nadaj stałej typ, a wszędzie zachowa się jak wartość tego typu, łącznie z tym, że Go odmówi mieszania typów:
const Limit int = 10
var f float64 = 2
fmt.Println(Limit * f)
invalid operation: Limit * f (mismatched types int and float64)
Z const Limit = 10 (beztypową) Limit * f się kompiluje i daje 20. Dlatego zostawiaj stałe beztypowe, chyba że typ niesie znaczenie. Niesie je wtedy, gdy deklarujesz stałe własnego nazwanego typu, bo tak Go buduje enumy:
type Weekday int
const (
Sunday Weekday = iota
Monday
Tuesday
)
Tutaj typ jest sednem: funkcja przyjmująca Weekday dokumentuje, czego oczekuje. iota i oparte na nim wzorce enumów opisuje strona o enumach i iota.
const a var
const | var | |
|---|---|---|
| Może się zmienić | nie | tak |
| Wartość znana | w czasie kompilacji | w czasie działania |
| Dozwolone typy | bool, liczbowe, string | dowolne |
| Zajmuje pamięć w czasie działania | nie | tak |
Można pobrać adres (&x) | nie | tak |
| Nieużyta to błąd | nie | tak, dla zmiennych lokalnych |
Używaj const do wszystkiego, co jest naprawdę stałe: limitów, rozmiarów, nazw, kodów protokołów, formatów tekstu. Kontrolę przepełnienia w czasie kompilacji i dokładną arytmetykę dostajesz za darmo.
Pułapki
Stałe typowane się nie dopasowują. const Timeout int = 5 nie przekażesz tam, gdzie oczekiwany jest time.Duration, bez konwersji; beztypową const Timeout = 5 można pomnożyć: Timeout * time.Second.
Na stałych całkowitych dzielenie jest całkowite. const Half = 1 / 2 to 0, bo oba argumenty są beztypowymi liczbami całkowitymi. Napisz 1.0 / 2, żeby dostać 0.5.
Brak adresu. &MaxRetries się nie kompiluje. Jeśli API chce *int, najpierw skopiuj stałą do zmiennej.
Najczęściej zadawane pytania
Jak zadeklarować stałą w Go?
Za pomocą const: const MaxRetries = 3 albo z jawnym typem const Timeout time.Duration = 5 * time.Second. Kilka stałych można zgrupować w bloku: const ( A = 1; B = 2 ). Wartość musi dać się obliczyć w czasie kompilacji.
Czy w Go można zrobić stały slice, mapę lub tablicę?
Nie. Stałe mogą być tylko wartościami logicznymi, liczbami (w tym runami) i stringami. const Colors = []string{"red"} kończy się błędem is not constant. Użyj var na poziomie pakietu, a jeśli wywołujący nie mogą go zmieniać, udostępnij funkcję zwracającą świeżą kopię.
Czym jest stała beztypowa (untyped) w Go?
To stała zadeklarowana bez typu, na przykład const Pi = 3.14159. Nie ma ustalonego typu, dopóki nie zostanie użyta, więc ta sama stała działa jako float32, float64 albo, jeśli wartość na to pozwala, int. Stałe beztypowe są też dokładne: kompilator trzyma co najmniej 256 bitów precyzji, więc const Big = 1 << 100 jest poprawne, o ile używasz jej tylko w wyrażeniach, których wynik się mieści.
Czym różni się const od var w Go?
const jest ustalane w czasie kompilacji i nigdy się nie zmienia; nie zajmuje pamięci w czasie działania programu i może przechowywać tylko bool, liczbę lub string. var to miejsce w pamięci, któremu można przypisać nową wartość, może mieć dowolny typ i przechowywać wartości obliczane w czasie działania, jak time.Now().