Область видимости и затенение
У каждого кухонного прибора есть свой ящик с продуктами. В ящике стоят коробки, а в коробках — банки поменьше:
- банку, поставленную в ящик, видно из любой коробки внутри него;
- банку из коробки снаружи коробки не видно;
- соседний прибор в чужой ящик не заглядывает.
Переменные в Go устроены так же: где переменную объявили, там её и видно.
Иногда это даёт неожиданный результат. Внутри if написано x := 10, но после блока x снова равна 1. Компилятор не сообщает об ошибке, потому что в программе две разные переменные с именем x:
package main
import "fmt"
func main() {
x := 1
if x > 0 {
x := 10
fmt.Println("inside:", x) // inside: 10
}
fmt.Println("outside:", x) // outside: 1
}Блоки и области видимости
Блок — это код в фигурных скобках: тело функции, ветка if, тело цикла. Где видно имя, зависит от того, где его объявили. Разберём четыре случая.
Переменная внутри блока
Область видимости такой переменной начинается с её объявления и заканчивается закрывающей скобкой блока:
- во вложенных блоках она видна;
- снаружи блока её нет: если внутри
ifобъявилиy, тоyпосле блока не скомпилируется с ошибкойundefined: y; - выше объявления её тоже нет, даже в том же блоке.
Переменная из заголовка for или if
Переменная из заголовка for и переменная из инициализации if принадлежат блоку своей инструкции. Переменная n из if n := 3; n > 5 видна в обеих ветках, и в else тоже. После инструкции этих переменных уже нет.
Параметры функции
Параметры видны во всём теле своей функции. Переменные двух разных функций друг друга не видят: у каждого прибора свой ящик.
Имена уровня пакета
Имя, объявленное вне функций через var или const, видно во всём пакете. Его можно читать в любой функции, даже в той, что написана выше объявления.
Затенение
Оператор := объявляет новую переменную в текущем блоке, даже если снаружи уже есть переменная с таким же именем. Внутренняя переменная скрывает внешнюю до конца своего блока, и это называют затенением.
Представьте, что в коробку поставили новую банку с той же надписью: пока вы в коробке, рука берёт ближнюю. Внешняя переменная при этом не меняется, и после блока имя снова означает её.
Давайте разберём первую программу по строкам:
x := 1создаёт внешнююx.- Условие
x > 0истинно, иx := 10создаёт вторуюxвнутриif. - Первый
Printlnпечатает её:inside: 10. - После закрывающей скобки внутренняя
xисчезает, и второйPrintlnпечатает внешнюю:outside: 1.
Здесь аналогия с банками заканчивается. Из коробки можно дотянуться до дальней банки, а в Go до конца блока имя x означает только внутреннюю переменную. Добраться до внешней по этому имени уже нельзя.
Затенение — не ошибка, но такой код трудно читать, поэтому внутренней переменной лучше дать другое имя.
Этот вопрос часто задают на собеседовании как задачу «что напечатает программа» с := внутри if. Ищите, в каком блоке объявлена каждая переменная: := во вложенном блоке создаёт новую, а внешняя остаётся прежней.
:= или =
Во вложенном блоке два оператора с одним именем x делают разные вещи.
:= — новая переменная
:= объявляет переменную в текущем блоке. Во вложенном блоке это затенение, как в первой программе. В одном блоке так объявить имя дважды нельзя: компилятор ответит no new variables on left side of :=.
= — та же переменная
= ничего не объявляет, а ищет имя в текущем блоке и снаружи. Поэтому, чтобы изменить внешнюю переменную, внутри блока пишут присваивание:
x := 1
if x > 0 {
x = 10
}
fmt.Println(x) // 10Внутри if нет своей x, и x = 10 меняет внешнюю. После блока программа печатает 10.
Частая ошибка
Сумму в цикле накапливают через :=:
total := 0
for i := 1; i <= 3; i++ {
total := total + i // ✗
fmt.Print(total, " ")
}
fmt.Println(total)Программа печатает 1 2 3 0. На каждой итерации := создаёт новую total из внешней, которая так и остаётся 0. Пишите total += i или total = total + i.
Главное
- Переменная видна от объявления до закрывающей скобки своего блока, включая вложенные блоки.
:=во вложенном блоке объявляет новую переменную, которая затеняет внешнюю с тем же именем.- Чтобы изменить внешнюю переменную внутри блока, пишут
=, а не:=.
Подпишись, чтобы пройти курс
Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.