Объявление функций, параметры, несколько возвращаемых значений и область видимости
Содержание курса
Пакетная и локальная область видимости: затенение переменных
Посмотрите на такой код:
package main
import "fmt"
var counter int = 10
func reset() {
counter := 0 // опасно!
fmt.Println("внутри reset:", counter) // 0
}
func main() {
reset()
fmt.Println("в main:", counter) // 10, а не 0
}
Переменная counter объявлена на уровне пакета через var — это пакетная переменная, доступная из всех функций в том же пакете. Но внутри reset стоит :=, и компилятор воспринимает это как объявление новой локальной переменной с тем же именем — потому что пакетная counter находится во внешней области видимости, а не в том же блоке. Обращения к counter в теле функции идут к локальной копии. После выхода из reset локальная переменная исчезает, а пакетная остаётся нетронутой.
Код компилируется без единого предупреждения — это и делает затенение коварным.
Исправление простое: чтобы изменить пакетную переменную, используйте присваивание =, а не :=:
func reset() {
counter = 0 // присваивание, не объявление
fmt.Println("внутри reset:", counter) // 0
}
Теперь reset действительно меняет counter, и main напечатает 0.
Точнее о поведении :=. В примере выше := создаёт новую переменную, потому что counter из другой (более широкой) области. Есть, однако, случай, когда := повторно использует уже объявленную переменную из того же блока: групповое присваивание вида value, err := .... Если err уже объявлена в том же блоке, а value — новая, компилятор переиспользует существующую err и объявит только value. Это не затенение — имя остаётся тем же объектом. Затенение возникает именно тогда, когда := применяется к имени из внешней области.
Общее правило: на уровне пакета объявление всегда через var; внутри функции := удобен, но требует внимания, когда имя совпадает с чем-то из внешней области — тогда вместо изменения существующей переменной вы получите её локальную копию.
