Модуль
1Hello, World!2Переменные в Go3Целые числа4Строки и руны5float64 и числа с плавающей точкой6if, switch, for← вы здесь7bool и логические операторы8Константы и iota9Пакет fmt: форматирование вывода
Урок 6~15 минут

if, switch, for

if / else

Скобки вокруг условия не нужны. Фигурные — обязательны:

go
x := 42
 
if x > 0 {
    fmt.Println("положительное")
} else if x < 0 {
    fmt.Println("отрицательное")
} else {
    fmt.Println("ноль")
}

Init statement: самая Go-идиоматичная конструкция

if принимает опциональный оператор инициализации перед условием:

go
if n := rand.Intn(100); n < 50 {
    fmt.Println("мало:", n)
} else {
    fmt.Println("много:", n)
}
// n здесь не существует

Переменная n живёт только внутри if/else. Это важно для обработки ошибок:

go
// Классический паттерн 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 принято проверять ошибки и выходить, а не вкладывать:

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

go
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 цепочке

go
score := 75
 
switch {
case score >= 90:
    fmt.Println("отлично")
case score >= 70:
    fmt.Println("хорошо")   // ← выполнится
case score >= 50:
    fmt.Println("удовлетворительно")
default:
    fmt.Println("неудовлетворительно")
}

Type switch — полиморфизм без интерфейса

go
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: когда нужен явно

go
n := 5
 
switch {
case n > 0:
    fmt.Println("положительное")
    fallthrough  // явно переходим к следующему
case n != 0:
    fmt.Println("ненулевое")  // тоже выполнится
}

Используется редко. В основном при реализации парсеров или когда несколько case должны делать одно и то же с добавкой.


for — один цикл для всего

В Go нет while, do-while, foreach. Только for. Но он справляется со всем:

Классический for

go
for i := 0; i < 5; i++ {
    fmt.Print(i, " ")  // 0 1 2 3 4
}

Три части (init; condition; post) — все опциональны.

For как while

go
n := 1
for n < 1000 {
    n *= 2
}
fmt.Println(n)  // 1024

Бесконечный цикл

go
for {
    conn, err := listener.Accept()
    if err != nil {
        log.Println(err)
        continue
    }
    go handleConn(conn)
}

Сервер принимает соединения вечно. for {} без условий — идиоматичный способ написать event loop.


range: итерация по коллекциям

range работает с разными типами:

go
// Слайс: (индекс, значение)
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 добавил итерацию по целому числу:

range по числу (Go 1.22)
Ctrl+Enter
1
go
// До 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
// ❌ 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
// ✅ 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. Аналогичный баг существовал во многих языках до явного введения блочного скоупинга.

Для старых версий фикс был такой:

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. Это открыло путь к пользовательским итераторам:

go
// Итератор — функция с 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
}

Три возможных сигнатуры итераторов:

go
func(yield func() bool)         // без значения
func(yield func(V) bool)        // одно значение
func(yield func(K, V) bool)     // два значения (как range по map)

Стандартная библиотека сразу воспользовалась этим:

go
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, метки

go
// 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 тоже есть, но используется крайне редко — в основном в сгенерированном коде и низкоуровневом рантайме.


Паттерны

Итерация с индексом и значением

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)

go
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])
}

Читаемый цикл с именованным диапазоном

go
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.

В Go один цикл — for. Нет while, нет do-while, нет foreach. Зато for умеет всё что они умеют, и ещё больше — range по целому числу с Go 1.22.
x := 42

if x > 0 {
    fmt.Println("положительное")
} else if x < 0 {
    fmt.Println("отрицательное")
} else {
    fmt.Println("ноль")
}
положительное
💡 Скобки вокруг условия не нужны. Фигурные — обязательны.
🎯
Миссия 1 из 5
Как называется синтаксис if n := f(); n > 0 { } в Go?