> В какой версии Go был исправлен баг с указателями в цикле (Go)

Уровень: middle · Роль: backend · Категория: Технические вопросы

Компании: Т-Банк

Стек: Go

> Пример ответа

Короткий ответ

Баг с захватом переменной цикла в замыканиях был исправлен в Go 1.22. До этой версии переменные, объявленные через for i := 0; ... или for _, v := range ..., переиспользовались на каждой итерации, что приводило к неожиданному поведению при передаче их в горутины или замыкания. Начиная с Go 1.22 каждая итерация получает собственную переменную, что соответствует интуитивному ожиданию большинства разработчиков.

Подробное объяснение

Проблема существовала с ранних версий Go. В цикле for переменные i и v объявлялись один раз и переиспользовались на каждой итерации. Если вы создавали замыкание или горутину внутри цикла, они захватывали одну и ту же переменную, а не её копию на момент итерации. К моменту выполнения замыкания переменная уже содержала значение последней итерации.

Это поведение было частым источником багов, особенно при работе с горутинами. Разработчикам приходилось использовать костыли: создавать локальную копию переменной внутри тела цикла или передавать значение как аргумент функции.

В Go 1.22 (февраль 2024) семантика была изменена. Теперь переменные, объявленные в заголовке цикла for, создаются заново на каждой итерации. Это изменение было внесено как часть более широкого улучшения работы с циклами и замыканиями. Для обратной совместимости старое поведение можно включить через GOEXPERIMENT=loopvar (до версии 1.22) или через //go:build теги в более новых версиях, но это не рекомендуется.

На практике

Для большинства проектов переход на Go 1.22 не требует изменений в коде - новое поведение просто устраняет класс ошибок. Однако если ваш код полагался на старое поведение (например, намеренно использовал последнее значение переменной во всех замыканиях), он может сломаться. Такие случаи редки и обычно являются багами сами по себе.

При обновлении версии Go стоит пересмотреть код, где внутри цикла создаются горутины или замыкания, и удалить лишние копии переменных, если они были добавлены как обходной путь.

Пример кода

До Go 1.22:

GO
var funcs []func()
for i := 0; i < 3; i++ {
funcs = append(funcs, func() {
fmt.Println(i) // выведет 3, 3, 3
})
}

После Go 1.22:

GO
var funcs []func()
for i := 0; i < 3; i++ {
funcs = append(funcs, func() {
fmt.Println(i) // выведет 0, 1, 2
})
}

Аналогично для range:

GO
for _, v := range []int{1, 2, 3} {
go func() {
fmt.Println(v) // теперь выведет 1, 2, 3, а не 3, 3, 3
}()
}

Как отвечать на собеседовании

Начните с точной версии - Go 1.22. Затем кратко объясните суть проблемы: переменная цикла переиспользовалась, и замыкания захватывали ссылку на неё. Упомяните, что это было неожиданно для многих разработчиков и требовало обходных путей. Подчеркните, что изменение семантики было обратно совместимым для подавляющего большинства корректного кода. Если спросят про детали, можно упомянуть GOEXPERIMENT=loopvar и то, что изменение касается только переменных, объявленных в заголовке цикла, а не внутри тела.

Что проверяет интервьюер

Интервьюер проверяет, знаете ли вы актуальные изменения в языке и понимаете ли вы, как работает захват переменных в замыканиях. Это также тест на внимательность к деталям: многие разработчики знают о баге, но не знают точной версии исправления. Дополнительно проверяется понимание семантики for и range, а также умение объяснять последствия изменения для существующего кода.

Типичные ошибки

  • Называть версию Go 1.21 или 1.20 - исправление вышло именно в 1.22.
  • Говорить, что проблема была только с range, - она касалась и классического for с счётчиком.
  • Утверждать, что исправление полностью ломает старый код - на практике это затрагивает только код, который намеренно использовал баг.
  • Путать захват переменной цикла с захватом переменных внутри тела цикла - последние и раньше создавались заново на каждой итерации.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью