Как устроена область видимости в Go?
Что проверяют
Знаете ли вы правила видимости в Go и связанное с ними затенение переменных. Вопрос кажется простым, но следом почти всегда идёт задачка на := внутри блока, где ответ не очевиден.
Как отвечать
Разложите на два разных механизма, их часто путают.
Первый это лексические блоки. Идентификатор виден внутри блока, где объявлен, и во всех вложенных:
- уровень пакета: переменные и функции вне тела функции видны во всех файлах пакета;
- уровень функции: параметры и локальные переменные;
- уровень блока: тело
if,for,switch, любые фигурные скобки. Заголовки тоже создают блок, поэтомуif err := do(); err != nilобъявляетerrтолько на время условия и обеих веток.
Второй это экспорт. Из другого пакета доступно только то, что начинается с заглавной буквы: Name виден снаружи, name нет. Правило одинаково работает для функций, типов, полей структур и методов, поэтому неэкспортируемое поле не попадёт и в JSON при сериализации. Пакет с сегментом internal в пути виден только внутри своего поддерева, это способ сделать общий код без публичного API.
Обязательно скажите про затенение: := требует хотя бы одну новую переменную слева, а остальные переиспользует. Внутри вложенного блока err := ... создаёт новую переменную вместо внешней, и присвоенное значение теряется при выходе из блока. Это классическая ошибка, go vet и линтер shadow её ловят.
Полезно добавить про переменную цикла: начиная с Go 1.22 на каждой итерации она новая, и старая ловушка с горутинами, замыкающими один и тот же i, ушла. На старых версиях значение приходилось копировать.
Где спотыкаются
Считают, что видимость определяется файлом. Она определяется пакетом, файлы внутри пакета друг другу не изолированы.
Не могут объяснить, почему после if переменная из его заголовка недоступна.
Забывают, что затенённая ошибка компилируется без предупреждения. Именно так теряются проверки err в реальном коде.