Разбор ошибок Go: что означает сообщение и как его исправить
Сообщение компилятора Go на английском выглядит сурово, но почти всегда объясняет проблему точно — просто словами, к которым привык не каждый. «declared and not used» — это не придирка, а предупреждение о потерянном значении. «cannot use x as string value» — не «неправильный тип», а требование явного преобразования. Ниже — словарь типичных ошибок и разбор каждой из них.
29 типовых ошибоксо ссылкой на урок
Ошибки, которые встречаются на второй неделе обучения
Go проверяет типы статически и строго. Нет неявных преобразований, нет «истины» для чисел и строк, нет молчаливого округления. Все эти правила выглядят как занудство ровно до первого часа работы с чужим кодом: вместо непонятной ошибки в рантайме вы получаете точное указание компилятора на строку и позицию.
Вторая группа ошибок — из рантайма: выход за границы среза, разыменование nil-указателя, отправка в закрытый канал, гонка данных. Их общий признак в том, что код компилируется, а ломается позже — под нагрузкой или на edge case. Именно их чаще всего находят на собеседовании и в код-ревью.
Интерактивный разбор ниже берёт текст ошибки, который вы скопировали из терминала, и находит совпадение в словаре: объяснение, способ исправления, пример кода и ссылку на урок, где тема разобрана. Полный словарь продублирован ниже в виде таблицы — он остаётся полезным, даже если вы не пользуетесь JavaScript.
Небольшая практическая деталь: вставлять нужно ключевую часть сообщения, а не весь лог. Компилятор печатает несколько строк — путь к файлу, номер, суть и список переменных; разбору достаточно строки про суть, например declared and not used или index out of range. Паника из лога сервиса годится целиком: в ней уже есть и суть, и стек.
Вставьте текст ошибки
Подойдёт сообщение компилятора, go vet, паника или ошибка из логов — даже вместе с номерами строк.
Частые ошибки — в один клик
Как читать сообщение компилятора
Сначала имя файла и строка. Go показывает путь файла, номер строки и колонки: ./main.go:18:13. Номер колонки указывает на конкретное выражение.
Затем суть в квадратных скобках. Сообщение устроено как «что случилось»: declared and not used: total,cannot use x as string value, missing return.
Потом контекст. В конце сообщения Go перечисляет переменные с их типами — это подсказка, почему преобразование не проходит.
Ошибок может быть несколько. Компилятор показывает их списком: исправление первой часто исправляет и следующую.
Словарь собран по урокам базового и продвинутого курсов Go. Он покрывает ошибки компиляции, паники рантайма, проверки go vet и типовые проблемы данных.
Словарь типичных ошибок Go
Полный список разборов: сообщение, причина, исправление и урок.
cannot use ... as ... value
Ошибка компиляции
cannot use x (variable of type int) as string value in argument to fmt.Println
Компилятор не может преобразовать значение автоматически: в Go нет неявных приведений типов. int и string — разные типы, и даже если «на глаз» они выглядят одинаково, преобразовать их нужно явно.
Исправление: Используйте явное преобразование strconv.Itoa(x) для числа в строку или fmt.Sprint(x), а для сравнения приведите типы к общему заранее. Следите за типами элементов срезов и карт: срез []int нельзя сравнивать со срезом []string.
Имя не найдено в текущей области видимости. Чаще всего это забытый импорт пакета (fmt, strings, errors) или опечатка в имени переменной. Если имя живёт в другом пакете — нужен импорт и префикс пакета.
Исправление: Добавьте импорт: import "fmt". Если компилятор подсказывает «declared and not used» рядом — проверьте, не перепутали ли вы имя переменной и имя пакета.
В Go переменная, объявленная внутри функции, обязана быть использована. Это защита от «мусорных» переменных: если посчитали значение и забыли вывести или вернуть — почти наверняка это ошибка. На уровне пакета правило не действует.
Исправление: Либо используйте переменную, либо замените объявление на пустое присваивание _: _, err = doSomething(). Не обходите правило массово — оно ловит реальные опечатки.
Функция объявлена с возвращаемым значением, но не во всех ветвях есть return. Компилятор требует, чтобы каждая ветвь завершалась возвратом — даже если кажется, что ветвь недостижима.
Исправление: Добавьте return в конце функции или измените сигнатуру. Для функций с несколькими результатами проверьте все ветви: ранний return в середине часто «забывает» один из результатов.
В условии if ожидается выражение типа bool. В Go нет «истинности» для чисел и строк, как в некоторых других языках: нельзя написать if count или if name.
Исправление: Сравнивайте явно: if count > 0, if name != "". Для указателей проверяйте на nil: if p != nil.
assignment mismatch: 2 variables but fmt.Sprintf returns 1 value
Число переменных слева от = не совпадает с числом возвращаемых значений. Частая причина — предположение, что функция возвращает два значения, когда возвращает один, или наоборот.
Исправление: Сверьтесь с сигнатурой функции. Если функция возвращает (значение, ошибку), то присваивать нужно два переменные: v, err := f(). Если нужна только ошибка — используйте _, err := f().
Прямое преобразование int в string в Go недопустимо: это не перевод символа, а попытка переиначить целое число в текст. Нужно явно сказать, что имеется в виду.
Исправление: Для текста: strconv.Itoa(x) или string(rune(x)). Для парсинга: strconv.Atoi(s). Для байтов: string([]byte{...}).
invalid operation: a + b (mismatched types int and string)
Оператор применён к типам, для которых он не определён: сложить число и строку, сравнить разные типы, применить арифметику к структуре.
Исправление: Приведите операнды к одному типу заранее. Для строк используйте конкатенацию через + только для строк, а числа переводите в текст через strconv.
panic: runtime error: index out of range [5] with length 3
Обращение к элементу за пределами среза или массива. В Go проверки границ нет: это решение ради скорости, и ошибка всплывает как паника в рантайме.
Исправление: Проверяйте границы перед обращением: if i < len(s). Для срезов не забывайте про slice bounds out of range при s[low:high] — high обязан быть не больше len, а low не больше high. В циклах используйте range, он безопаснее.
panic: runtime error: invalid memory address or nil pointer dereference
Разыменование указателя, который равен nil. Обычно это структура с методом, вызванным на nil-указателе, или поле, которое не заполнили при создании объекта.
Исправление: Проверяйте err сразу после вызова, не забывайте инициализировать указатели и карты. Метод с приёмником-указателем умеет проверять nil сам: func (s *Service) Handle() { if s == nil { return } ... }.
В канал отправляют после close. Закрытый канал означает «новых значений не будет», поэтому отправка в него — нарушение контракта и паника.
Исправление: Закрывает канал всегда отправитель, и только когда он закончил отправлять. Если отправителей несколько, закрытие согласовывают через sync.Once или отдельный канал-сигнал. Получатель канал не закрывает.
Канал закрывают повторно или закрывают nil-канал. Обе ситуации означают, что владение каналом нарушено: закрывать должен ровно один отправитель.
Исправление: Закрывайте канал в одном месте — обычно в функции-владельце после завершения всех отправок. Для повторного закрытия используйте sync.Once: once.Do(func() { close(ch) }).
Целочисленное деление на ноль — паника рантайма. В отличие от вещественной арифметики, где результатом будет Inf или NaN, целые числа так не ведут себя.
Исправление: Проверяйте знаменатель до деления. Если ноль — это штатная ситуация, возвращайте ошибку: if div == 0 { return 0, errors.New("деление на ноль") }.
Счётчик WaitGroup ушёл в минус: Done() вызвали больше раз, чем Add(). Обычно это ошибка в учёте задач, когда часть воркеров завершается не по своему пути.
Исправление: Считайте задачи: wg.Add(n) до запуска, ровно один wg.Done() на задачу. Частая ошибка — defer wg.Done() внутри цикла без учёта, что горутина запускается на каждой итерации.
fatal error: all goroutines are asleep - deadlock!
Все горутины заблокированы, и ни одна не может проснуться. В Go это не «зависание» молча: рантайм обнаруживает состояние и завершает программу с сообщением fatal error.
Исправление: Добавьте таймаут: select с case <-time.After(d) или context.WithTimeout. Проверьте, что канал действительно кто-то читает и что все ветки select имеют путь выхода.
Карту читали и меняли из разных горутин одновременно. Карта в Go не потокобезопасна: внутренняя структура может остаться в несогласованном состоянии, и рантайм аварийно останавливает программу.
Исправление: Закройте карту sync.RWMutex (или sync.Map, если паттерн доступа действительно такой). Не забывайте RLock на чтении. Копия карты (maps.Clone) тоже не решает проблему — нужна синхронизация.
WARNING: DATA RACE
Write at 0x00c000104010 by goroutine 8:
Гонка данных: две горутины обращаются к одной переменной, хотя бы одна из них пишет, и нет синхронизации. Программа может работать «правильно» годами и сломаться в любой момент.
Исправление: Запустите тесты с детектором гонок: go test -race ./... Затем добавьте мьютекс, атомики или канал — в зависимости от того, что вы делаете с данными. Гонки не лечатся «случайным» порядком выполнения.
Запись в карту, которая не была создана через make. Чтение из nil-карты допустимо (вернёт нулевые значения), а запись — нет: рантайм паникует.
Исправление: Создайте карту: m := make(map[string]int). Для структуры с полем-картой инициализируйте его в конструкторе или через указатель на структуру (иначе все структуры поделят одну карту).
Контекст отменили по таймауту (deadline) или явно (cancel). Внутри это обычная ошибка: запрос не успел завершиться в отведённое время либо вызывающая сторона прекратила работу.
Исправление: Проверяйте ctx.Err() и возвращайте ошибку наверх. Не забывайте cancel(): context.WithCancel требует вызвать cancel, иначе контекст и его ресурсы живут до конца родительского. Таймауты задавайте на уровне HTTP-клиента и базы данных.
EOF — конец потока данных, это не ошибка, а нормальный сигнал «данных больше нет». unexpected EOF означает, что поток оборвался на полуслове: файл прочитан не до конца, соединение оборвалось, ответ сервера пришёл неполным.
Исправление: Различайте io.EOF и другие ошибки: if errors.Is(err, io.EOF) { return nil }. unexpected EOF обычно означает повреждённые данные или оборванное соединение — проверьте целостность файла и код ответа сервера.
Соединение не установилось: сервис не слушает порт, DNS не резолвит имя или соединение упало по таймауту. В docker-compose это самая частая причина «сервис не видит базу».
Исправление: Проверьте, что процесс слушает порт, что адрес внутри контейнера — имя сервиса, а не localhost. Для Go добавьте таймаут на http.Client и context на запросы к базе.
Запрос ожидал ровно одну строку (QueryRow + Scan), а строк не оказалось: записи нет или их несколько. Это не «сбой базы», а штатная ситуация, которую нужно обработать.
Исправление: Используйте err := db.QueryRow(...).Scan(&v) и проверяйте errors.Is(err, sql.ErrNoRows) — это не ошибка, а «не найдено». Если строк может быть много — используйте Query со строками.
panic: interface conversion: interface {} is nil, not *sql.DB
Из интерфейса попытались достать значение конкретного типа, а внутри интерфейса лежит nil. Значение с пустым интерфейсом (any) остаётся nil до присваивания непустого значения.
Исправление: Проверяйте интерфейс на nil перед приведением типа, используйте форму с двумя значениями (v, ok := x.(T)) и обрабатывайте ok == false. Не оборачивайте nil-указатель в интерфейс — проверяйте указатель.
Строка в Go неизменяема: это последовательность байтов, а не массив. Изменить символ в строке нельзя — компилятор запрещает это на этапе сборки.
Исправление: Соберите новую строку: []byte(s) для байтов, []rune(s) для символов. Для замены подстроки используйте strings.Replace, для склейки — strings.Builder.
Функцию, возвращающую несколько значений, использовали там, где ожидается одно. Например, передали f() в другую функцию, которая принимает один аргумент.
Исправление: В Go передать несколько значений можно только в вызове с точно совпадающим числом параметров. В остальных случаях разнесите по переменным: a, b := f().
fmt.Printf format %d has arg s of wrong type string
go vet нашёл несоответствие между строкой формата и типом аргумента. %d ждёт число, а вы передали строку. Код скомпилируется, но напечатает мусор в виде %!d(string=hello).
Исправление: Замените глагол на подходящий (%s для строк, %v для универсального вывода) или преобразуйте значение. Запускайте go vet ./... (или golangci-lint) в CI — такие ошибки ловятся до продакшена.
predeclared any requires go1.18 or later (-lang was set to go1.17)
В коде используется возможность из более новой версии языка, а в go.mod указана более старая. Компилятор строго соблюдает версию языка из директивы go — это гарантирует, что проект соберётся старым инструментарием.
Исправление: Обновите директиву в go.mod: go mod edit -go=1.22 и обновите toolchain, если нужно. Новая версия языка включается только явным изменением go.mod — это осознанное решение команды разработчиков.
nil в Go — не универсальный «пустой указатель». Он допустим только для указателей, срезов, карт, каналов, функций и интерфейсов. Для int, string и struct это не имеет смысла.
Исправление: Используйте нулевое значение типа: 0, "", struct{}{}. Если поле структуры должно быть необязательным — сделайте его указателем или добавьте флаг «задано».
Почему в Go такая строгая проверка неиспользуемых переменных?
Потому что это ловит целый класс опечаток. Если вы посчитали total, но забыли его вывести или вернуть, почти наверняка где-то потерялось значение. Правило «объявленная переменная должна использоваться» работает только внутри функций: на уровне пакета переменные живут дольше и могут быть частью API.
Что делать с паникой index out of range?
Сначала найдите место: в Go паника сообщает номер строки и имя функции. Проверьте границы перед обращением и не забывайте, что у среза есть и длина, и ёмкость. Если индекс приходит извне — отсекайте его на входе, а не внутри обработчика. Циклы по срезу лучше писать через range: он не выйдет за границы.
Чем ошибка отличается от паники в Go?
Ошибка — это ожидаемый результат работы, который возвращается по соглашению (value, err) и обрабатывается вызывающим кодом. Паника — это аварийная остановка текущей горутины: она означает, что программа столкнулась с ситуацией, которую не предусмотрели. Панику можно перехватить через recover, но лечить ею ошибки не стоит: panic нужен для ситуаций уровня «программа не может продолжить работу», например, нарушен инвариант при инициализации.
Нужен ли go vet в повседневной работе?
Да, это самый дешёвый способ поймать ошибки форматирования и подозрительные конструкции. Запускайте go vet ./... локально и в CI, а перед релизом — golangci-lint с набором проверок. Большинство находок vet — это реальные баги: неверный глагол в Printf, потерянная копия мьютекса, неиспользуемый результат функции.
Сообщение компилятора Go почти всегда указывает на конкретную причину: тип не реализует интерфейс, нельзя преобразовать, переменная не используется, вызов не совпадает с сигнатурой. Проблема в том, что новичок читает текст, а не понимает правило, которое за текстом стоит.
Словарь разбирает типовые ошибки на части: что означает сообщение, какое правило Go нарушено, минимальный исправленный пример и ссылка на урок, где это правило разобрано. Так сообщение перестаёт быть препятствием и становится точкой входа в тему.
Почему ошибок много в одном месте
Одна неверная сигнатура порождает десяток сообщений: сначала ругается на тип, потом на каждый вызов, который ждёт другой набор аргументов. Проверка идёт сверху вниз по стеку компилятора, и исправлять нужно первое сообщение — остальные исчезнут сами.
Как пользоваться
1Вставьте текст ошибки целиком. Нужен весь блок сообщений, а не только последняя строка: по нему видно, от чего сообщения отталкиваются.
2Начните с первой ошибки в списке. Компилятор идёт сверху вниз, и каждая следующая ошибка часто является следствием предыдущей.
3Перейдите по ссылке на урок. Разбор ошибки объясняет правило, а не только этот случай: следующая похожая ошибка вы уже прочитаете сами.
Когда это пригождается
Сообщение на две страницы
Обычно это одна причина с большим последствием. Найдите первое сообщение — чаще всего оно и есть корень проблемы.
Ошибка появляется после добавления зависимости
Проверьте версию модуля и имя интерфейса: несовпадение версий даёт сообщения, которые выглядят как ошибки вашего кода.
Ошибка только в CI
Чаще всего расходятся версии Go или зависимостей между машиной и сборкой. Сравните go.mod и версию рантайма.
Словарь покрывает типовые ошибки компилятора и паники. Сообщения от инструментов статического анализа и от гонок данных имеют другой формат — для них нужен другой инструмент.