Константа это значение, зафиксированное на этапе компиляции. Объявите её через const, и Go отвергнет любую попытку её изменить.
Константы можно объявлять на уровне пакета или внутри функции, по одной на строку или группой в блоке const ( ... ). MB и GB вычисляются из других констант, и компилятор выполняет эту арифметику один раз, при сборке.
Присваивание константе, например MaxRetries = 5, это ошибка компиляции:
./main.go:8:2: cannot assign to MaxRetries (neither addressable nor a map index expression)
Что может быть константой
Только три вида значений: булевы, числа (целые, с плавающей точкой, комплексные и руны) и строки. Значение должен уметь вычислить компилятор: литералы, другие константы, арифметика над ними и несколько встроенных функций вроде len от константной строки.
Всё, что требует выполнения кода или выделения памяти, исключено:
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
Почему в Go нет константных слайсов и мап
Слайсы, мапы и содержащие их структуры это ссылки на память, которая существует во время выполнения, поэтому компилятор не может вшить их в программу как фиксированные значения, а модификаторов readonly или final для переменных в Go нет. Типичные обходные пути:
Копия стоит одной аллокации на вызов, и для данных размером с конфигурацию это нормально. Для поиска по ключу есть ещё один вариант, похожий на константу: switch внутри функции, func statusText(code int) string { switch code { ... } }.
Нетипизированные константы
Константа, объявленная без типа, называется нетипизированной. У неё есть вид (целое, число с плавающей точкой, руна, строка, bool), но нет конкретного типа Go, пока она не использована, и она подстраивается под контекст:
Ratio работает как int, float64, uint8 и float32 без всякого преобразования. Поэтому и компилируется time.Sleep(2 * time.Second): 2 нетипизирована и становится time.Duration.
Когда контекст не требует типа, как в x := Ratio, константа получает тип по умолчанию:
| Вид нетипизированной константы | Тип по умолчанию |
|---|---|
целое (42) | int |
с плавающей точкой (4.2) | float64 |
руна ('a') | rune (int32) |
комплексное (2i) | complex128 |
| строка | string |
| булево | bool |
Точная арифметика
Нетипизированные числовые константы точны. Компилятор представляет целые числа как минимум 256 битами, поэтому промежуточные значения могут быть намного больше любого типа Go:
Саму Huge нельзя напечатать как целое число: при передаче в fmt.Println она преобразуется в int, и компилятор это ловит (float64(Huge) работает, потому что float вмещает такую величину):
cannot use Huge (untyped int constant 1267650600228229401496703205376) as int value in argument to fmt.Println (overflows)
Та же проверка на этапе компиляции останавливает и ошибки поменьше, например var b byte = 300:
cannot use 300 (untyped int constant) as byte value in variable declaration (overflows)
Типизированные константы
Дайте константе тип, и она везде ведёт себя как значение этого типа, включая отказ Go смешивать типы:
const Limit int = 10
var f float64 = 2
fmt.Println(Limit * f)
invalid operation: Limit * f (mismatched types int and float64)
С const Limit = 10 (нетипизированной) выражение Limit * f компилируется и даёт 20. Поэтому оставляйте константы нетипизированными, если тип не несёт смысла. Смысл он несёт, когда вы объявляете константы собственного именованного типа; так в Go строятся перечисления:
type Weekday int
const (
Sunday Weekday = iota
Monday
Tuesday
)
Здесь тип и есть суть: функция, принимающая Weekday, сама документирует, что ей нужно. iota и шаблоны перечислений на его основе разобраны на странице про enum и iota.
const и var
const | var | |
|---|---|---|
| Может меняться | нет | да |
| Значение известно | на этапе компиляции | во время выполнения |
| Допустимые типы | bool, числовые, string | любые |
| Занимает память во время выполнения | нет | да |
Можно взять адрес (&x) | нет | да |
| Неиспользуемая это ошибка | нет | да, для локальных |
Используйте const для всего, что действительно фиксировано: лимиты, размеры, имена, коды протоколов, строки формата. Проверки переполнения на этапе компиляции и точная арифметика достаются бесплатно.
Подводные камни
Типизированные константы не подстраиваются. const Timeout int = 5 нельзя передать туда, где ожидается time.Duration, без преобразования; нетипизированную const Timeout = 5 можно умножить: Timeout * time.Second.
Деление целочисленных констант целочисленное. const Half = 1 / 2 равна 0, потому что оба операнда нетипизированные целые. Напишите 1.0 / 2, чтобы получить 0.5.
Нет адреса. &MaxRetries не компилируется. Если API нужен *int, сначала скопируйте константу в переменную.
Часто задаваемые вопросы
Как объявить константу в Go?
Через const: const MaxRetries = 3 или с явным типом: const Timeout time.Duration = 5 * time.Second. Несколько констант можно сгруппировать в блок: const ( A = 1; B = 2 ). Значение должно вычисляться на этапе компиляции.
Можно ли сделать константный слайс, мапу или массив в Go?
Нет. Константами могут быть только булевы значения, числа (включая руны) и строки. const Colors = []string{"red"} не компилируется с ошибкой is not constant. Используйте var на уровне пакета, а если вызывающий код не должен его менять, отдавайте наружу функцию, которая возвращает свежую копию.
Что такое нетипизированная константа в Go?
Константа, объявленная без типа, например const Pi = 3.14159. У неё нет фиксированного типа, пока она не использована, поэтому одна и та же константа работает как float32, как float64 или, если позволяет значение, как int. Нетипизированные константы ещё и точные: компилятор хранит не меньше 256 бит точности, поэтому const Big = 1 << 100 допустима, если использовать её только в выражениях, результат которых помещается в тип.
Чем const отличается от var в Go?
const фиксируется на этапе компиляции и никогда не меняется; во время выполнения она не занимает память и может хранить только bool, число или строку. var это ячейка памяти, которую можно переприсвоить, и она может хранить любой тип, в том числе значения, вычисленные во время выполнения, например time.Now().