if, switch, for
if / else
Скобки вокруг условия не нужны. Фигурные — обязательны:
x := 42
if x > 0 {
fmt.Println("положительное")
} else if x < 0 {
fmt.Println("отрицательное")
} else {
fmt.Println("ноль")
}Init statement: самая Go-идиоматичная конструкция
if принимает опциональный оператор инициализации перед условием:
if n := rand.Intn(100); n < 50 {
fmt.Println("мало:", n)
} else {
fmt.Println("много:", n)
}
// n здесь не существуетПеременная n живёт только внутри if/else. Это важно для обработки ошибок:
// Классический паттерн Go:
if err := json.Unmarshal(data, &result); err != nil {
return fmt.Errorf("decode failed: %w", err)
}
// err нет в скопе — не загрязняет пространство имён
// Ещё пример — открытие файла:
if f, err := os.Open("config.json"); err != nil {
log.Fatal(err)
} else {
defer f.Close()
// работаем с f
}Early return вместо вложенности
В Go принято проверять ошибки и выходить, а не вкладывать:
// ❌ Плохо — глубокая вложенность ("arrow anti-pattern")
func process(path string) error {
if file, err := os.Open(path); err == nil {
if data, err := io.ReadAll(file); err == nil {
if result, err := parse(data); err == nil {
save(result)
} else {
return err
}
} else {
return err
}
} else {
return err
}
return nil
}
// ✅ Хорошо — flat с early return
func process(path string) error {
file, err := os.Open(path)
if err != nil { return err }
defer file.Close()
data, err := io.ReadAll(file)
if err != nil { return err }
result, err := parse(data)
if err != nil { return err }
return save(result)
}switch
В Go switch намного удобнее, чем в C/Java:
- Нет неявного fallthrough — каждый
caseавтоматическиbreak - Можно группировать значения через запятую
- Работает без выражения (замена
if-else ifцепочке)
Базовый switch
status := 404
switch status {
case 200, 201:
fmt.Println("успех")
case 301, 302:
fmt.Println("редирект")
case 400:
fmt.Println("плохой запрос")
case 404:
fmt.Println("не найдено") // ← выполнится
default:
fmt.Println("неизвестный статус")
}Switch без выражения — замена длинной if-else цепочке
score := 75
switch {
case score >= 90:
fmt.Println("отлично")
case score >= 70:
fmt.Println("хорошо") // ← выполнится
case score >= 50:
fmt.Println("удовлетворительно")
default:
fmt.Println("неудовлетворительно")
}Type switch — полиморфизм без интерфейса
func describe(i any) string {
switch v := i.(type) {
case nil:
return "nil"
case int:
return fmt.Sprintf("int(%d)", v)
case float64:
return fmt.Sprintf("float64(%.2f)", v)
case string:
return fmt.Sprintf("string(%q)", v)
case bool:
return fmt.Sprintf("bool(%t)", v)
case []int:
return fmt.Sprintf("[]int с %d элементами", len(v))
default:
return fmt.Sprintf("неизвестный тип: %T", v)
}
}
describe(42) // "int(42)"
describe(3.14) // "float64(3.14)"
describe("привет") // `string("привет")`
describe([]int{1,2,3}) // "[]int с 3 элементами"Переменная v внутри каждого case уже приведена к нужному типу — никакого ручного приведения.
fallthrough: когда нужен явно
n := 5
switch {
case n > 0:
fmt.Println("положительное")
fallthrough // явно переходим к следующему
case n != 0:
fmt.Println("ненулевое") // тоже выполнится
}Используется редко. В основном при реализации парсеров или когда несколько case должны делать одно и то же с добавкой.
for — один цикл для всего
В Go нет while, do-while, foreach. Только for. Но он справляется со всем:
Классический for
for i := 0; i < 5; i++ {
fmt.Print(i, " ") // 0 1 2 3 4
}Три части (init; condition; post) — все опциональны.
For как while
n := 1
for n < 1000 {
n *= 2
}
fmt.Println(n) // 1024Бесконечный цикл
for {
conn, err := listener.Accept()
if err != nil {
log.Println(err)
continue
}
go handleConn(conn)
}Сервер принимает соединения вечно. for {} без условий — идиоматичный способ написать event loop.
range: итерация по коллекциям
range работает с разными типами:
// Слайс: (индекс, значение)
for i, v := range []int{10, 20, 30} {
fmt.Printf("[%d]=%d ", i, v)
}
// [0]=10 [1]=20 [2]=30
// Map: (ключ, значение) — порядок случайный!
for k, v := range map[string]int{"a": 1, "b": 2} {
fmt.Printf("%s=%d ", k, v)
}
// Строка: (байтовый индекс, rune)
for i, r := range "Hi мир" {
fmt.Printf("%d:%c ", i, r)
}
// 0:H 1:i 2: 3:м 5:и 7:р
// Канал: значения до закрытия
for msg := range ch {
fmt.Println(msg)
}range — целое число (Go 1.22)
Go 1.22 добавил итерацию по целому числу:
// До Go 1.22:
for i := 0; i < 10; i++ { }
// Go 1.22+:
for i := range 10 {
fmt.Print(i, " ") // 0 1 2 3 4 5 6 7 8 9
}Это официальная возможность, закреплённая в спецификации языка. Активируется автоматически при go 1.22 в go.mod.
Переменные цикла в Go 1.22: исправленный баг
До Go 1.22 все итерации цикла делили одну переменную. Это вызывало коварные баги:
// ❌ Go 1.21 и ниже — баг с замыканиями
funcs := make([]func(), 3)
for i := 0; i < 3; i++ {
funcs[i] = func() { fmt.Println(i) }
}
funcs[0]() // 3 ← все печатают 3!
funcs[1]() // 3
funcs[2]() // 3
// i к моменту вызова уже = 3// ✅ Go 1.22+ — каждая итерация создаёт новую i
funcs := make([]func(), 3)
for i := range 3 {
funcs[i] = func() { fmt.Println(i) }
}
funcs[0]() // 0
funcs[1]() // 1
funcs[2]() // 2Это одно из самых долгожданных исправлений в истории Go. Аналогичный баг существовал во многих языках до явного введения блочного скоупинга.
Для старых версий фикс был такой:
for i := 0; i < 3; i++ {
i := i // создаём новую переменную с тем же именем
funcs[i] = func() { fmt.Println(i) }
}Итераторы: range over functions (Go 1.23)
Go 1.23 сделал возможным передавать функцию в range. Это открыло путь к пользовательским итераторам:
// Итератор — функция с yield-параметром
func Evens(n int) func(yield func(int) bool) {
return func(yield func(int) bool) {
for i := 0; i < n; i += 2 {
if !yield(i) { // yield возвращает false при break
return
}
}
}
}
for v := range Evens(10) {
fmt.Print(v, " ") // 0 2 4 6 8
}Три возможных сигнатуры итераторов:
func(yield func() bool) // без значения
func(yield func(V) bool) // одно значение
func(yield func(K, V) bool) // два значения (как range по map)Стандартная библиотека сразу воспользовалась этим:
import "slices"
import "maps"
// slices.All — итератор по слайсу с индексом
for i, v := range slices.All([]string{"a", "b", "c"}) {
fmt.Printf("%d:%s ", i, v)
}
// slices.Backward — итерация в обратном порядке
for i, v := range slices.Backward([]int{1, 2, 3}) {
fmt.Printf("%d:%d ", i, v)
}
// 2:3 1:2 0:1
// maps.Keys — только ключи
for k := range maps.Keys(m) {
fmt.Println(k)
}break, continue, метки
// continue — пропустить итерацию
for i := range 10 {
if i%2 == 0 { continue }
fmt.Print(i, " ") // 1 3 5 7 9
}
// break — выйти из цикла
for i := range 100 {
if i*i > 50 { break }
fmt.Print(i, " ") // 0 1 2 3 4 5 6 7
}
// Метки — break/continue внешнего цикла
outer:
for i := range 3 {
for j := range 3 {
if i+j >= 3 {
break outer // выходим из обоих циклов
}
fmt.Printf("(%d,%d) ", i, j)
}
}
// (0,0) (0,1) (0,2) (1,0) (1,1)goto в Go тоже есть, но используется крайне редко — в основном в сгенерированном коде и низкоуровневом рантайме.
Паттерны
Итерация с индексом и значением
items := []string{"go", "python", "rust"}
// Нужно и то и другое:
for i, item := range items {
fmt.Printf("%d. %s\n", i+1, item)
}
// Только значения:
for _, item := range items { }
// Только индексы:
for i := range items { }Параллельная итерация (Go 1.22+ style)
keys := []string{"a", "b", "c"}
vals := []int{1, 2, 3}
for i := range min(len(keys), len(vals)) {
fmt.Printf("%s=%d\n", keys[i], vals[i])
}Читаемый цикл с именованным диапазоном
const maxRetries = 3
for attempt := range maxRetries {
if err := connect(); err == nil {
break
}
fmt.Printf("попытка %d не удалась\n", attempt+1)
time.Sleep(time.Second)
}В следующем модуле разберём функции Go — variadic, замыкания и defer.