Перейти к содержимому
ГоМастер

Поиск по урокам

Найдите урок, курс, термин или раздел

Что изменилось в Go: от 1.18 до 1.25

Go выпускается по расписанию: раз в квартал выходит новая версия с новыми возможностями языка и библиотек. Новости о релизах читают все, но пересматривают код — единицы. Здесь каждое изменение объяснено языком задачи: что появилось, что это даёт и на каком курсе тему можно закрыть.

8 релизов, 27 измененийфильтр по темам и по вашей версии

Зачем следить за версиями

Язык Go меняется медленно и предсказуемо: новые версии не ломают обратную совместимость, но заметно меняют то, как пишутся привычные вещи. Самый яркий пример — Go 1.22, где переменная цикла стала создаваться заново на каждой итерации. Код, который раньше считался корректным, перестал ловиться детектором гонок, а новые проекты можно писать без лишних копий переменной.

Другие изменения заметно меньше по масштабу, но по влиянию на ежедневную работу сопоставимы: пакеты slices и maps убрали самописные хелперы, log/slog дал структурированное логирование в стандартной библиотеке, а ошибки.Join — способ собрать несколько ошибок в одну без потери цепочки. А в Go 1.25 появился WaitGroup.Go, который убирает самый частый источник паник в конкурентном коде.

Ниже — таймлайн с фильтром по темам. Выберите свою версию компилятора, и изменения, которых у вас ещё нет, будут помечены: это и есть самый практичный повод обновиться.

Отдельный сюжет — совместимость. Go обещает обратную совместимость в рамках ветки 1.x, поэтому обновление почти никогда не ломает код. Но новые версии приносят не только возможности: в релизе могут быть исправления безопасности и ускорения, о которых вы не узнаете из заметок. Практика простая: держите версию, которая вышла не меньше чем полгода назад, и обновляйтесь регулярно, а не раз в год. К каждому изменению ниже приложено объяснение «что это даёт тебе» — без него список релизов превращается в справочник, который никто не читает.

Что изменилось в Go

Выберите свою версию компилятора — увидите, что появилось после неё. Отметьте темы, чтобы отфильтровать список.

Доступно вам
15 возможностей

Появились после Go 1.21 — их не было в вашем компиляторе.

Фильтр по темам

Go 1.18

март 2022

Дженерики и фаззинг — два главных события года

Дженерики (type parameters)

дженерикиязык

Можно писать обобщённые функции и типы: одна реализация для любого типа. Это убирает копипаст в контейнерах и утилитах. Цена — сложность чтения: применяйте дженерики там, где дублирование реально повторяется, а не «на всякий случай».

func Map[T, U any](s []T, f func(T) U) []U {
    out := make([]U, 0, len(s))
    for _, v := range s {
        out = append(out, f(v))
    }
    return out
}

Фаззинг в testing

тесты

Тест получает случайные входные данные и ищет падение. Отлично работает для парсеров, структур данных и кодеков: находит падежи, о которых вы не думали.

func FuzzParse(f *testing.F) {
    f.Add("[]byte{1, 2}")
    f.Fuzz(func(t *testing.T, data []byte) {
        Parse(data) // упадёт — fuzzer сохранит вход в testdata
    })
}

any вместо interface{}

язык

Пустой интерфейс теперь можно писать как any — короче и читается как «любой тип». В остальном поведение не изменилось.

Go 1.19

август 2022

Атомарные типы и экспериментальные арены памяти

Типы в sync/atomic

конкурентность

Вместо ручного atomic.AddInt64(&counter, 1) появились готовые типы atomic.Int64, atomic.Bool, atomic.Pointer[T]. Читать их можно обычным обращением — типобезопасно и без гонок.

var hits atomic.Int64

hits.Add(1)
fmt.Println(hits.Load())

Экспериментальные арены памяти

производительность

arena.Arena позволяет выделять и освобождать пачку объектов разом. Эксперимент (GOEXPERIMENT=arenas), в обычном коде не нужен: GC в Go и так умеет собирать короткоживущие объекты.

Go 1.20

февраль 2023

errors.Join и преобразование среза в массив

errors.Join

ошибки

Ошибки из нескольких источников (валидация полей, параллельные запросы) складываются в одну, а errors.Is находит любую из них. Раньше приходилось придумывать свой тип ошибки.

if err := validate(req); err != nil {
    return errors.Join(err, ctx.Err())
}

Преобразование среза в массив

срезы и карты

Срез можно превратить в массив или указатель на массив: a := (*[4]byte)(b). Это удобно для системных вызовов вроде unix.Read, которые требуют фиксированный буфер.

buf := make([]byte, 4)
n, err := unix.Read(fd, (*[4]byte)(buf))

unsafe.String и unsafe.SliceData

производительность

Новые функции для работы с памятью без копирования. Полезно в высокопроизводительном коде и в обёртках над syscall, но требует понимания, что данные не меняются и живут дольше вызова.

Go 1.21

август 2023

slices и maps в стандартной библиотеке, log/slog, min и max

Пакеты slices и maps

срезы и карты

Частые операции переехали в стандартную библиотеку: slices.Sort, slices.Contains, slices.Clip, maps.Keys, maps.Values. Код становится короче, а поведение — предсказуемым (keys возвращает слайс в случайном порядке — сортируйте, если нужен порядок).

slices.Sort(users)
slices.SortFunc(users, func(a, b User) int { return a.Name < b.Name })
keys := slices.Sorted(maps.Keys(m))

Встроенные min, max и clear

язык

min(a, b), max(a, b) и clear(map) — без импорта и без аллокаций. Для срезов clear(s) обнуляет элементы, но сохраняет ёмкость.

log/slog — структурные логи

инструменты

Стандартный структурированный логгер: ключи и значения, уровни, JSON-вывод. Можно уйти с самописных логгеров на стандартную библиотеку и не тянуть зависимость.

slog.Info("order created",
    "order_id", id,
    "amount", amount,
)

Управление версией инструментария

инструменты

В go.mod появилась директива toolchain: можно указать минимальную версию Go, и сборка сама подтянет нужный компилятор. Команда из разных версий перестаёт ломать сборку.

Go 1.22

февраль 2024новее вашей версии

Новая переменная цикла и шаблоны маршрутов в net/http

Переменная цикла создаётся заново на каждой итерации

языкконкурентность

Самый обсуждаемый релиз Go. Раньше переменная цикла была одна на весь цикл, поэтому её часто ловили горутины (классическая утечка указателя). Теперь каждая итерация получает свою переменную — старый код работает корректно, а знание об этом стоит перепроверить.

for _, v := range values {
    go func() { process(v) }() // теперь v — своя на итерацию
}

Шаблоны маршрутов в net/http

http и сеть

Стандартный сервер умеет разбирать путь с параметрами: mux.HandleFunc("GET /items/{id}", h) и r.PathValue("id"). Для простых сервисов теперь не нужен внешний роутер.

mux := http.NewServeMux()
mux.HandleFunc("GET /items/{id}", func(w http.ResponseWriter, r *http.Request) {
    id := r.PathValue("id")
})

Таймеры больше не держат память

производительность

Раньше неиспользованный time.Timer удерживал объекты до срабатывания. Теперь таймеры, которые никто не ждёт, собираются сборщиком мусора — меньше утечек в сервисах с таймаутами.

Go 1.23

август 2024новее вашей версии

range по целым числам и по функциям, пакет iter

range по целому числу

язык

for i := range 10 { ... } — цикл по числам без переменной-счётчика. Мелкая, но приятная деталь: код становится ближе к математической записи.

for i := range 10 {
    fmt.Println(i)
}

range по функции-итератору

языксрезы и карты

Итерируемые последовательности: можно писать свои конвейеры и ленивые структуры. Появился пакет iter с типами Seq и функциями, чтобы такие итераторы выглядели одинаково у всех.

func Count(n int) iter.Seq[int] {
    return func(yield func(int) bool) {
        for i := 0; i < n; i++ {
            if !yield(i) {
                return
            }
        }
    }
}

slices.Collect, slices.Sorted, maps.Keys и maps.Values

срезы и карты

Стандартная библиотека научилась превращать итераторы в срезы и отдавать ключи карты. Раньше для этого писали свои хелперы в каждом проекте.

Go 1.24

февраль 2025новее вашей версии

Обобщённые псевдонимы типов, os.Root, очистка мусора по времени

Обобщённые псевдонимы типов

дженерики

Теперь можно писать type MyMap[K comparable, V any] = map[K]V. Полезно для алиасов сложных обобщённых типов в публичных API.

os.Root — безопасная работа с файлами

инструменты

Ограничивает работу с файловой системой каталогом-корнем: путь не выйдет за пределы папки, даже если в нём есть «..». Защита от path traversal в сервисах, которые принимают пути от пользователя.

root, err := os.OpenRoot("/var/data")
defer root.Close()

f, err := root.Open("uploads/1.txt")

runtime.AddCleanup и слабые ссылки

производительность

AddCleanup — современная замена runtime.SetFinalizer: объект можно «освободить» детерминированно, когда он больше не нужен. Слабые ссылки позволяют не мешать сборщику мусора удерживать объект.

Новая внутренняя реализация map

производительность

Карты переписаны на алгоритм швейцарских таблиц: меньше памяти и быстрее операции с большими картами. Изменение внутреннее — API не изменился, но бенчмарки на map стали заметно лучше.

testing.B.Loop

тесты

Новый цикл бенчмарков: b.Loop() сам разгоняет тест, убирает ручную настройку b.N и корректнее работает с таймерами. Меньше шансов написать бенчмарк, который ничего не измеряет.

Go 1.25

август 2025новее вашей версии

Детерминированные тесты горутин, WaitGroup.Go, GOMAXPROCS для контейнеров

WaitGroup.Go

конкурентность

Вместо wg.Add(1); go func() { defer wg.Done(); ... }() теперь есть wg.Go(func() { ... }). Меньше шансов забыть Done и словить панику negative WaitGroup counter.

var wg sync.WaitGroup
for _, job := range jobs {
    wg.Go(func() { process(job) })
}
wg.Wait()

testing/synctest — тесты с горутинами без гонок

тестыконкурентность

Пакет позволяет тестировать код с горутинами детерминированно: виртуальное время, никаких sleep и никаких флаки-тестов. Тесты конкурентного кода перестают быть лотереей.

func TestTimeout(t *testing.T) {
    synctest.Test(t, func(t *testing.T) {
        // виртуальное время: минута пройдёт мгновенно
    })
}

GOMAXPROCS с учётом контейнеров

конкурентностьпроизводительность

В контейнере Go учитывает лимиты CPU: GOMAXPROCS подстраивается под cgroup, а не под число ядер хоста. Сервис в лимите 2 CPU больше не создаёт планировщик на 64 ядра.

Экспериментальный сборщик мусора Green Tea

производительность

Новый алгоритм сборки мусора (эксперимент) снижает накладные расходы на сборку и уменьшает задержки. Включается через GOEXPERIMENT, в продакшене пока осторожно.

Как обновляться без боли

  1. Поставьте новую версию. go install golang.org/dl/go1.25@latest или скачайте архив с официального сайта.
  2. Обновите директиву в go.mod. Команда go mod edit -go=1.25 поднимает минимальную версию языка, а go get toolchain@go1.25.0 — версию компилятора.
  3. Прогоните тесты и go vet. Изменение версии языка может включить новое поведение: например, переменная цикла в Go 1.22 стала новой на итерацию. Тесты и vet поймают несовместимость.
  4. Не гонитесь за каждой версией. Новые возможности появляются каждый квартал, но стабильная ветка Go 1.x живёт долго: для продакшена обычно достаточно версии, вышедшей полгода назад.

Частые вопросы о версиях Go

Какую версию Go ставить новичку?

Самую свежую стабильную: язык и библиотеки развиваются, а старые версии не получают исправлений безопасности. Если вы работаете в команде — версию диктует go.mod проекта и CI. Единственный случай, когда стоит остаться на старой версии, — зависимость от пакета, который ещё не поддерживает новый стандарт.

Что изменилось в переменной цикла в Go 1.22 и нужно ли переписывать код?

До Go 1.22 переменная цикла была одна на весь цикл, и её адрес могли унести в горутину — отсюда классические утечки и «последний элемент» вместо ожидаемого. С Go 1.22 на каждой итерации своя переменная, и такой баг больше не воспроизводится. Переписывать код не нужно: поведение старых программ сохранилось корректным. Но знать об этом полезно, чтобы не бояться горутину внутри цикла и не писать лишние копии переменной.

Когда использовать errors.Join, а когда fmt.Errorf с %w?

fmt.Errorf с %w оборачивает одну ошибку, добавляя контекст, и сохраняет цепочку для errors.Is и errors.As. errors.Join складывает несколько ошибок в одну — удобно, когда параллельно проверяли несколько полей или источников. Объединённую ошибку тоже можно развернуть через errors.Is: он найдёт любой из компонентов.

Что даёт range по функции-итератору?

Возможность писать ленивые последовательности и возвращать их из функции: цикл for x := range seq будет вызывать yield, пока тот не вернёт false. Это удобно для потоков, деревьев, ленивых вычислений и для того, чтобы не тянуть зависимость вроде iterators. В Go 1.23 появился пакет iter с типами Seq, Seq2 и хелперами, чтобы такие итераторы выглядели одинаково.

Что изучить дальше