В Go есть два способа объявить переменную: var, который работает где угодно, и :=, короткая форма, которая работает внутри функций и выводит тип сама.
У каждой переменной фиксированный тип, определённый на этапе компиляции. Если вы задаёте значение, Go выводит тип из него, поэтому указывать тип дополнительно (var city string = "Lisbon") можно, но избыточно.
var и :=
var | := | |
|---|---|---|
| Где | на уровне пакета и внутри функций | только внутри функций |
| Тип | необязателен; обязателен, если нет значения | всегда выводится |
| Начальное значение | необязательно (иначе нулевое значение) | обязательно |
| Повторное объявление | никогда | можно, если хотя бы одна переменная слева новая |
Идиоматичный выбор внутри функции:
x := value, когда есть начальное значение.var x T, когда нужно нулевое значение; это сигнал «пусто намеренно»:var buf bytes.Buffer,var total int.var x T = value, когда выведенный тип не тот, что нужен, напримерvar ratio float64 = 1(простоratio := 1дало быint).
:= вне функции это синтаксическая ошибка:
./main.go:5:1: syntax error: non-declaration statement outside function body
Нулевые значения
Переменная, объявленная без значения, никогда не содержит мусор. В ней лежит нулевое значение её типа:
| Тип | Нулевое значение |
|---|---|
| целые, числа с плавающей точкой | 0 |
bool | false |
string | "" |
| указатели, слайсы, мапы, каналы, функции, интерфейсы | nil |
| массивы, структуры | каждый элемент или поле получает своё нулевое значение |
Хорошие API на Go проектируют так, чтобы нулевое значение было полезным: нулевой sync.Mutex не заблокирован, нулевой bytes.Buffer пуст и готов к работе, а len, range и append работают с nil-слайсом. Осторожнее с мапой: чтение из nil-мапы возвращает нулевые значения, но запись в неё вызывает панику. Создавайте мапы через make или литерал, прежде чем что-то в них записывать.
Объявление нескольких переменных
Блок var ( ... ) группирует связанные переменные уровня пакета. При множественном присваивании вроде x, y = y, x все выражения справа вычисляются до того, как что-либо присваивается, поэтому обмен и работает.
:= с несколькими именами ничего не переобъявляет, если хотя бы одно имя слева новое. Уже существующим именам в той же области видимости значение просто присваивается. Именно это позволяет переиспользовать err:
f, err := os.Open("a.txt") // declares f and err
g, err := os.Open("b.txt") // declares g, assigns to the existing err
Если ни одно имя не новое, это ошибка:
./main.go:7:4: no new variables on left side of :=
Чтобы присвоить значение уже существующей переменной, используйте обычное =.
Неиспользуемые переменные это ошибка
Локальная переменная, которую объявили, но ни разу не прочитали, останавливает сборку:
./main.go:9:2: declared and not used: x
Это не предупреждение, которое можно отключить. Удалите переменную, используйте её или на время отладки заглушите пустым идентификатором: _ = x. Пустой идентификатор также отбрасывает ненужные возвращаемые значения: _, err := fmt.Println("hi").
На переменные уровня пакета и параметры функций это правило не распространяется.
Область видимости и затенение
Переменная существует от объявления до конца охватывающего блока ({ ... }). := во внутреннем блоке создаёт новую переменную, даже если снаружи уже есть переменная с таким именем. Внешняя переменная затеняется, а не меняется:
Программа печатает inside: 42, а затем outside: 0. := внутри if объявил и count, и err как новые переменные внутреннего блока, потому что там их ещё не было. Код компилируется без ошибок и является классическим источником багов «моё значение пропало».
Исправление: объявить err заранее и присваивать через =:
var err error
count, err = strconv.Atoi(input)
go vet по умолчанию не сообщает о затенении. Это делает отдельный анализатор shadow, и линтеры вроде golangci-lint умеют его включать.
Именование
- Используйте
camelCase:maxRetries,userID. Неmax_retries. - Аббревиатуры пишите в одном регистре:
userID,httpClient,parseURL, а неuserId. - Короткие имена для коротких областей видимости:
iв цикле,rдля reader,errдля ошибки. Для переменных уровня пакета имена длиннее и описательнее. - Имя с заглавной буквы на уровне пакета (
var Timeout) экспортируется в другие пакеты. См. раздел о пакетах и импортах.
Переменные уровня пакета
Переменные, объявленные вне функций, живут всё время работы программы и инициализируются до запуска main в порядке зависимостей. Они удобны для конфигурации и кешей, и их видят все горутины. Если в такую переменную пишет больше одной горутины, защитите её мьютексом или используйте sync/atomic, иначе получите гонку данных.
Для значений, которые никогда не меняются, лучше подходят константы. const нельзя изменить случайно, и во время выполнения она ничего не стоит.
Часто задаваемые вопросы
Чем var отличается от := в Go?
:= объявляет и инициализирует переменную с выведенным типом и работает только внутри функций: count := 10. var работает везде (в том числе на уровне пакета), позволяет указать тип и может обойтись без начального значения, тогда переменная получит нулевое значение: var count int. Внутри функций идиоматичный Go использует :=, когда есть начальное значение, и var, когда нужно нулевое.
Что такое нулевые значения в Go?
Любая переменная, объявленная без значения, получает нулевое значение своего типа: 0 для чисел, false для bool, "" для string и nil для указателей, слайсов, мап, каналов, функций и интерфейсов. У структуры каждое поле получает своё нулевое значение. Неинициализированных переменных в Go нет.
Как исправить ошибку «declared and not used» в Go?
Go отвергает локальную переменную, которой присвоили значение, но ни разу не прочитали. Используйте её, удалите или на время отладки присвойте пустому идентификатору: _ = x. На переменные уровня пакета и параметры функций правило не распространяется.
Как объявить глобальную переменную в Go?
Используйте var на уровне пакета, вне любой функции: var counter int. Короткая форма := там запрещена. Имя с заглавной буквы (var Counter int) видно другим пакетам, со строчной только своему пакету. Если в переменную уровня пакета пишут горутины, защитите её мьютексом.