Menu

Переменные в Golang: var, :=, нулевые значения и затенение

Как объявлять переменные в Go через var и короткую форму :=, что такое нулевые значения, множественное присваивание, а также ошибки затенения и неиспользуемых переменных, на которых спотыкаются новички.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

В 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
boolfalse
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) видно другим пакетам, со строчной только своему пакету. Если в переменную уровня пакета пишут горутины, защитите её мьютексом.

Coddy programming languages illustration

Учитесь программировать с Coddy

НАЧАТЬ