Как создать и использовать собственные типы ошибок в Go

19.01.2026 • Время чтения — 5 минут

Содержание
Иногда шутят, что 50% кода на Go — это обработка ошибок. Может, это и правда. Но мы точно знаем, что без хороших ошибок разбираться с проблемами очень сложно.

В этой статье пройдемся по возможностям Go для создания собственных ошибок и их обработки.

Оборачивание ошибок

В Go есть встроенный пакет errors, который предоставляет функции для работы с ошибками. Одна из ключевых возможностей — оборачивание ошибок с помощью функции fmt.Errorf и спецификатора %w:
size, err := strconv.Atoi(sizeStr)
if err != nil {
    return fmt.Errorf("convert size from string: %w", err)
}
В результате такого оборачивания получается цепочка ошибок, где каждая содержит предыдущую. Это позволяет сохранять контекст ошибок, что очень полезно для отладки — по этим «хлебным крошкам» можно понять, где именно случилась проблема.

Если ошибка нужна только для того, чтобы записать ее в лог, то этого вполне достаточно. Но не менее часто нужно уметь различать разные типы ошибок, чтобы по разному на них реагировать. Для этого в Go есть два основных подхода: ошибки-объекты и ошибки-типы.

Ошибки-объекты

Когда вы пишете подобный код на Go, вы создаете переменные с ошибкой:
var (
    ErrInvalidSize = errors.New("invalid size")
    ErrNotFound = errors.New("not found")
)
Теперь их можно использовать, чтобы разметить разные типы ошибок:
size, err := strconv.Atoi(sizeStr)
if err != nil {
    return fmt.Errorf("%w: convert from string: %w", ErrInvalidSize, err)
}
if size <= 0 {
    return fmt.Errorf("%w: should be positive", ErrInvalidSize)
}
if _, ok := blockSizes\[size\]; !ok {
    return ErrNotFound
}
И затем отличить одну ошибку от другой в месте обработки:
if errors.Is(err, ErrInvalidSize) {
    return httpError{http.StatusBadRequest, "Size is invalid."}
} else if errors.Is(err, ErrNotFound) {
    return httpError{http.StatusNotFound, "Block with provided size not found."}
}
Обратите внимание, что в этом примере мы используем errors.Is, чтобы проверить, что ошибка содержит определенную ошибку-объект.

Это важно, потому что они могут быть обернуты с помощью %w, и простое сравнение err == ErrInvalidSize не сработает.

Также важно понимать, что если объявить две ошибки с одинаковым текстом, то это будут два разных объекта. Поиск идет не по тексту, а по конкретному объекту. Ошибку-объект удобно использовать для обозначения категорий ошибок, но они не несут дополнительной информации. Ее можно включить в текст ошибки:
return fmt.Errorf("%w: size %d is not supported", ErrInvalidSize, size)
Но если нужно передать переменные данные вместе с ошибкой, то лучше использовать ошибки-типы.

Ошибки-типы

Ошибки в Go — это просто интерфейс с одним методом Error() string, поэтому можно создавать собственные типы ошибок, реализующие этот интерфейс:
type ValidationError struct {
    Field string
    Problem string
}

func (e *ValidationError) Error() string {
    return fmt.Sprintf("validation error on field %q: %s", e.Field, e.Problem)
}
Теперь можно создавать ошибки этого типа с дополнительными данными:
size, err := strconv.Atoi(sizeStr)
    if err != nil {
        return &ValidationError {
            Field: "size",
            Problem: "not a valid integer",
        }
    }

if size <= 0 {
    return &ValidationError{
        Field: "size",
        Problem: "should be positive",
        }
    }
В месте обработки ошибки нужно использовать errors.As, чтобы достать ошибку определенного типа, и получить доступ к дополнительным данным:
var ve *ValidationError
    if errors.As(err, &ve) {
        return httpError{http.StatusBadRequest, fmt.Sprintf("Invalid value for field %s: %s.", ve.Field, ve.Problem)}
    }
На этом возможности собственных типов ошибок не заканчиваются. Можно реализовать дополнительные методы, с которыми умеет работать пакет errors.

В первую очередь рекомендуем реализовать метод Unwrap() error, если одна ошибка оборачивает другую. Иначе информация о вложенной ошибке потеряется и ее нельзя будет достать с помощью errors.Unwrap, errors.Is и errors.As.

Это и многое другое мы разбираем в курсе по Golang для начинающих

Для учебы нужно знать основы Go — мы не будем изучать его с самого нуля. Наш курс подходит для разработчиков, которые уже знают базу или хотят перейти в Golang из другого языка программирования (C++, Java, Python и других) или стека: например, frontend, системного администрирования или mobile-разработки

Мы воссоздадим атмосферу работы внутри IT-компании и с нуля напишем таск-трекер на Golang. Обучение будет строиться не от теории к практике, а наоборот. Начнем писать код уже с первого занятия, а в теорию будем углубляться точечно. Получили рабочую задачу, прочитали ТЗ, посмотрели теорию — написали код.

Изучение теории, как на курсах с нуля, не дает практических навыков. База есть, а как что-то написать на работе — непонятно. И на собеседованиях показать нечего, а без опыта не берут.

В нашем сценарии ты получишь боевой опыт разработки на Go и сможешь показывать его на собеседованиях. Это выделит тебя на фоне остальных новичков без коммерческого опыта.

Набор уже открыт

Другие статьи

    В Go это обычное возвращаемое значение функции, а не механизм управления потоком выполнения. Функция возвращает результат и error, и ты сам решаешь, что с ним делать: проверить, вернуть выше по стеку, залогировать или остановить программу. Вся обработка ошибок происходит там, где это действительно уместно.

    Язык программирования Golang изначально спроектирован так, чтобы ошибки были частью обычного потока выполнения программы. Для этого используется interface error и обычная строка — error string. Почти любая функция может возвращать значения вместе с ошибкой, например, int и error. Поэтому ты всегда видишь, где возможна проблема, и сам решаешь, как на нее реагировать.

    Тип error — это type error interface error с единственным методом Error() string. Благодаря этому любая структура или тип может реализовать ошибку. Чаще всего ты встретишь return errors.New("...") или return fmt.Errorf("..."), где fmt позволяет удобно формировать сообщение. Такой подход делает обработка ошибок явной и читаемой: ты не ловишь исключения где-то в глубине стека, а работаешь с обычными значениями.

    В package main, внутри func main, ошибки обычно проверяют сразу после вызова функции. Классический пример: err return, затем проверка if err != nil. В этот момент и происходит обработка: ты можешь просто return errors выше по стеку, вывести сообщение через log или println, либо завершить программу через panic, если продолжать выполнение небезопасно. Использование nil fmt и аккуратных сообщений помогает быстрее понять причину сбоя.

    Иногда для ошибок используют struct с дополнительными полями: код, тип, исходные значения. Такая обработка удобна, когда нужно передать больше информации вызывающему коду и принять решение на его стороне.

    Для начинающих программистов Go это ключевая идея: ошибка — это данные. Последовательная проверка, понятные сообщения и аккуратная обработка делают программы предсказуемыми, простыми для отладки и удобными для поддержки по мере роста проекта.
    В Go работа с ошибками - это осознанная обработка значений, а не исключения, как в python