Переменные в Go
Переменные — три закона Go
В Go три правила, которые сразу отличают его от других языков:
1. Объявил — используй. Компилятор не соберёт программу с неиспользуемой переменной. Это не предупреждение — это ошибка:
Это кажется раздражающим поначалу. Но это дисциплина: в Go-коде нет мусорных переменных, которые «может пригодятся».
2. Тип выводится автоматически.
Не нужно писать int или string — Go смотрит на значение и сам решает:
name := "Alice" // Go знает: это string
count := 42 // Go знает: это int
pi := 3.14 // Go знает: это float643. Нулевые значения.
Каждая переменная без явного значения получает «ноль» для своего типа. Нет undefined, нет null для базовых типов.
Два способа объявить переменную
var — явный способ
var name string = "Alice"
var age int = 30
var pi float64 = 3.14
var ok bool = trueМногословно, но иногда нужно — особенно на уровне пакета (вне функций). := там не работает.
Можно опустить тип — Go выведет сам:
var name = "Alice" // тип: string
var age = 30 // тип: intМожно опустить значение — переменная получит нулевое значение:
var name string // ""
var count int // 0
var flag bool // false:= — короткое объявление
name := "Alice"
age := 30Это самый частый способ. Правила:
- Работает только внутри функций
- Хотя бы одна переменная слева должна быть новой
- Компилятор сам выводит тип
func main() {
x := 10 // новая переменная x
x, y := 20, 5 // x переиспользуется, y — новая
// x теперь 20, y = 5
}Нулевые значения
Это одна из сильных сторон Go. Ни одна переменная не бывает «пустой» в плохом смысле:
| Тип | Нулевое значение |
|---|---|
int, int64, uint, ... | 0 |
float32, float64 | 0.0 |
string | "" (пустая строка) |
bool | false |
pointer, slice, map, chan, func | nil |
Это означает: если ты объявил структуру с полями — все поля уже инициализированы. Нет NullPointerException при обращении к полям struct.
Множественное объявление
Несколько переменных одной строкой:
a, b := 10, 20
x, y, z := "hello", true, 3.14Групповое объявление через var — удобно на уровне пакета:
var (
host = "localhost"
port = 8080
debug = false
)Классический своп без временной переменной:
a, b := 1, 2
a, b = b, a // теперь a=2, b=1Это не магия — Go вычисляет правую часть целиком, потом присваивает. В других языках для этого нужна temp.
Область видимости (scope)
Переменная живёт в том блоке {}, где объявлена. Вышел из блока — переменная уничтожена:
func main() {
x := 10 // x видна в main
if true {
y := 20 // y видна только здесь
fmt.Println(x, y) // OK
}
fmt.Println(x) // OK
fmt.Println(y) // ОШИБКА: undefined: y
}Shadowing — переменная прячет другую
Это одна из самых коварных особенностей Go. Если внутри вложенного блока объявить переменную с тем же именем — она не изменит внешнюю, а создаст новую, которая закрывает (shadows) старую.
var x = 1 // пакетная переменная
func main() {
x := 2 // новая x в main, пакетная x теперь недоступна
if true {
x := 3 // ещё одна новая x, x=2 теперь недоступна
fmt.Println(x) // 3
}
fmt.Println(x) // 2 — вернулась x из main
}
fmt.Println(x) // здесь x = 1 — пакетнаяИнтерактивный визуализатор выше показывает, как переменные накапливаются в «стеке» при входе в блоки и исчезают при выходе.
Почему shadowing опасен
err := doSomething()
if err != nil {
err := fmt.Errorf("wrapped: %w", err) // ← ОШИБКА ДИЗАЙНА
// это новая err, не та что снаружи!
log.Fatal(err)
}
// здесь err — старая, необёрнутаяИспользуй = вместо := если хочешь изменить существующую переменную, а не создать новую.
Пустой идентификатор _
Go требует использовать все объявленные переменные. Но иногда нужно «выбросить» значение. Для этого есть специальный идентификатор _:
// Функция возвращает два значения
file, err := os.Open("data.txt")Если ошибка не нужна (хотя так делать не стоит):
file, _ := os.Open("data.txt") // игнорируем errНормальные случаи для _:
// Только значения из range, индекс не нужен
for _, v := range numbers {
fmt.Println(v)
}
// Импорт только для side effects (запускает init())
import _ "net/http/pprof"
// Деструктуризация, когда нужна только часть
x, _, z := threeValues()_ — это не переменная. Ты не можешь прочитать из неё значение. Это буквально «мусорная корзина».
Проверка типа переменной
Если хочешь узнать какой тип вывел Go — используй fmt.Printf с форматом %T:
x := 42
y := 3.14
z := "hello"
fmt.Printf("%T\n", x) // int
fmt.Printf("%T\n", y) // float64
fmt.Printf("%T\n", z) // string%T — тип переменной. %v — значение в дефолтном формате. Эти два формата ты будешь использовать постоянно при отладке.
Частые ошибки новичков
1. := на уровне пакета:
package main
x := 10 // СИНТАКСИЧЕСКАЯ ОШИБКА
var x = 10 // OK2. Shadowing при обработке ошибок:
result, err := step1()
if err == nil {
result, err := step2() // ← создаёт НОВЫЕ переменные!
// внешние result и err не изменены
}3. Объявить и не использовать:
func main() {
count := 0
// используй count или удали
fmt.Println("done") // ОШИБКА: count declared and not used
}В следующем уроке разберём целочисленные типы Go — int, int8/16/32/64, uint, и почему важно понимать переполнение.
var x = 1