Область видимости и пакеты

Область видимости (scope) определяет, где доступна переменная. В Go это интуитивно - но есть подводные камни.

Уровни видимости

Уровень пакета

package main

var globalVar = "я видна везде в пакете"

const maxRetries = 3

func main() {
    fmt.Println(globalVar)    // OK
    fmt.Println(maxRetries)   // OK
}

func otherFunc() {
    fmt.Println(globalVar)    // OK - тоже видна
}

Уровень функции

func calculate() {
    result := 42                // видна только внутри calculate
    fmt.Println(result)         // OK
}

func main() {
    calculate()
    // fmt.Println(result)      // ОШИБКА: result не определена
}

Уровень блока

func main() {
    x := 10

    if x > 5 {
        y := 20               // видна только внутри if
        fmt.Println(x, y)     // OK: x=10, y=20
    }

    // fmt.Println(y)          // ОШИБКА: y не определена

    for i := 0; i < 3; i++ {
        temp := i * 2          // видна только внутри for
        fmt.Println(temp)
    }

    // fmt.Println(i)          // ОШИБКА: i не определена
    // fmt.Println(temp)       // ОШИБКА: temp не определена
}

Shadowing - затенение переменных

Самый коварный подводный камень Go:

func main() {
    x := 10
    fmt.Println(x) // 10

    if true {
        x := 20    // НОВАЯ переменная x, затеняет внешнюю!
        fmt.Println(x) // 20
    }

    fmt.Println(x) // 10 - внешняя не изменилась!
}
Это частая причина багов: ```go var err error if true { result, err := doSomething() // err - НОВАЯ переменная! fmt.Println(result) } // err здесь всё ещё nil! ```

Как избежать shadowing

// ПЛОХО: shadowing err
var err error
if true {
    result, err := doSomething() // новый err
    _ = result
}
// err всё ещё nil

// ХОРОШО: присваивание в существующую
var err error
var result int
if true {
    result, err = doSomething() // = вместо :=
}

Утилита для поиска shadowing:

go install golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow@latest
shadow ./...

Экспорт: заглавная буква = публичный

В Go нет ключевых слов public/private. Регистр первой буквы решает всё:

package user

type User struct {
    Name  string  // Экспортируемое (публичное)
    Email string  // Экспортируемое
    age   int     // НЕ экспортируемое (приватное)
}

func NewUser(name string) *User { // Экспортируемая функция
    return &User{
        Name: name,
        age:  defaultAge(),       // вызов приватной функции
    }
}

func defaultAge() int { // НЕ экспортируемая
    return 0
}

Из другого пакета:

package main

import "myapp/user"

func main() {
    u := user.NewUser("Alice") // OK
    fmt.Println(u.Name)        // OK
    // fmt.Println(u.age)      // ОШИБКА: age не экспортировано
    // user.defaultAge()       // ОШИБКА: не экспортировано
}

<ComparisonTable data={{ headers: ["Имя", "Видимость", "Примеры"], rows: [ ["Name, DoSomething, MaxSize", "Публичное (экспортируемое)", "Доступно из других пакетов"], ["name, doSomething, maxSize", "Приватное", "Только внутри своего пакета"], ["_name, ", "Blank identifier", " = игнорировать значение"] ] }} />

Организация пакетов

myproject/
├── go.mod
├── main.go          // package main
├── config/
│   └── config.go    // package config
├── handler/
│   └── user.go      // package handler
└── model/
    └── user.go      // package model
// model/user.go
package model

type User struct {
    ID   int
    Name string
}
// main.go
package main

import "myproject/model"

func main() {
    u := model.User{ID: 1, Name: "Alice"}
    fmt.Println(u)
}

init() - автоматическая инициализация

package config

var Settings map[string]string

func init() {
    // Вызывается автоматически при импорте пакета
    Settings = make(map[string]string)
    Settings["env"] = "development"
}

Порядок: переменные пакета → init() → main().

Старайся не злоупотреблять init(). Явная инициализация через конструкторы обычно понятнее.

Зарегистрируйтесь бесплатно, чтобы пройти квиз, решить задание с автопроверкой и вести прогресс.