Перейти к содержимому
Теория · Функции · урок 7 из 10

Область видимости и затенение

У каждого кухонного прибора есть свой ящик с продуктами. В ящике стоят коробки, а в коробках — банки поменьше:

  • банку, поставленную в ящик, видно из любой коробки внутри него;
  • банку из коробки снаружи коробки не видно;
  • соседний прибор в чужой ящик не заглядывает.

Переменные в Go устроены так же: где переменную объявили, там её и видно.

Иногда это даёт неожиданный результат. Внутри if написано x := 10, но после блока x снова равна 1. Компилятор не сообщает об ошибке, потому что в программе две разные переменные с именем x:

GO
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, видно во всём пакете. Его можно читать в любой функции, даже в той, что написана выше объявления.

Затенение

Оператор := объявляет новую переменную в текущем блоке, даже если снаружи уже есть переменная с таким же именем. Внутренняя переменная скрывает внешнюю до конца своего блока, и это называют затенением.

Представьте, что в коробку поставили новую банку с той же надписью: пока вы в коробке, рука берёт ближнюю. Внешняя переменная при этом не меняется, и после блока имя снова означает её.

Давайте разберём первую программу по строкам:

  1. x := 1 создаёт внешнюю x.
  2. Условие x > 0 истинно, и x := 10 создаёт вторую x внутри if.
  3. Первый Println печатает её: inside: 10.
  4. После закрывающей скобки внутренняя x исчезает, и второй Println печатает внешнюю: outside: 1.

Здесь аналогия с банками заканчивается. Из коробки можно дотянуться до дальней банки, а в Go до конца блока имя x означает только внутреннюю переменную. Добраться до внешней по этому имени уже нельзя.

Затенение — не ошибка, но такой код трудно читать, поэтому внутренней переменной лучше дать другое имя.

Этот вопрос часто задают на собеседовании как задачу «что напечатает программа» с := внутри if. Ищите, в каком блоке объявлена каждая переменная: := во вложенном блоке создаёт новую, а внешняя остаётся прежней.

:= или =

Во вложенном блоке два оператора с одним именем x делают разные вещи.

:= — новая переменная

:= объявляет переменную в текущем блоке. Во вложенном блоке это затенение, как в первой программе. В одном блоке так объявить имя дважды нельзя: компилятор ответит no new variables on left side of :=.

= — та же переменная

= ничего не объявляет, а ищет имя в текущем блоке и снаружи. Поэтому, чтобы изменить внешнюю переменную, внутри блока пишут присваивание:

GO
x := 1
if x > 0 {
    x = 10
}
fmt.Println(x) // 10

Внутри if нет своей x, и x = 10 меняет внешнюю. После блока программа печатает 10.

Частая ошибка

Сумму в цикле накапливают через :=:

GO
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 и подпишись на канал — откроются все уроки.

Войти другим способом