Перейти к содержимому
Теория · Ошибки · урок 2 из 11

errors.New и fmt.Errorf

В прошлом уроке ошибка была накладной, которая приходит вместе с посылкой. Объявлять для каждой такой накладной свой тип с методом Error() — много кода ради одной строки. Для обычного случая в стандартной библиотеке есть две готовые функции. Давайте разберём каждую.

Две функции для ошибок

Обе функции возвращают значение типа error, и его возвращают из функции, как любое значение. Отличаются они тем, каким может быть текст.

errors.New

Функция errors.New из пакета errors создаёт ошибку с заданным текстом. Она принимает ровно одну строку:

GO
package main

import (
    "errors"
    "fmt"
)

func main() {
    err := errors.New("age is negative")
    fmt.Println(err)        // age is negative
    fmt.Println(err == nil) // false
}

Давайте разберём пример по строкам:

  1. errors.New("age is negative") создаёт ошибку и кладёт её в err.
  2. Первый Println печатает текст ошибки: age is negative.
  3. Второй печатает false: созданная ошибка не равна nil.

errors.New похожа на готовый штамп «товар повреждён»: текст одинаковый при каждом оттиске. Её берут, когда текст всегда один и тот же. Ограничение — текст задан раз и навсегда: подставить в него число errors.New не умеет.

Здесь картинка со штампом неточна. Два оттиска одного штампа читаются как одна и та же пометка. А два вызова errors.New("age is negative") дают две разные ошибки: текст у них совпадает, но сравнение == между ними даёт false.

fmt.Errorf

Когда в сообщение нужно вставить значение, пишите fmt.Errorf. Это уже пометка от руки: «коробка 3 из 5 помята». Эта функция форматирует строку по тем же правилам, что и fmt.Sprintf, но возвращает не string, а error. Знаки те же, что у Printf:

  • %d — для числа;
  • %q — для строки в кавычках;
  • %v — для любого значения, в том числе для другой ошибки.

Функция checkAge пользуется обеими функциями:

GO
func checkAge(n int) error {
    if n < 0 {
        return errors.New("age is negative")
    }
    if n > 150 {
        return fmt.Errorf("age %d is too big", n)
    }
    return nil
}
GO
fmt.Println(checkAge(200)) // age 200 is too big
fmt.Println(checkAge(30))  // <nil>

Давайте разберём checkAge:

  1. Для отрицательного возраста она возвращает ошибку с постоянным текстом через errors.New.
  2. Для возраста больше 150 она вызывает fmt.Errorf: в тексте нужно само число. Вызов checkAge(200) печатает age 200 is too big.
  3. Для допустимого возраста функция возвращает nil, и fmt.Println печатает <nil>.

Знаки %w и %v

Другую ошибку в fmt.Errorf можно вставить двумя знаками, и результат у них разный.

Знак %w

Знак %w оборачивает уже готовую ошибку: новая ошибка хранит старую внутри, и функция errors.Is позже находит её там. Это как приколоть старую накладную к новой: текст новый, а исходная пометка осталась при нём.

Знак %v

Знак %v вставляет только текст, а саму ошибку не сохраняет. Он переписывает старую пометку своими словами, и исходной ошибки внутри новой уже нет.

Какую функцию выбрать

Функцию выбирают только по тексту ошибки:

  • текст всегда одинаковый — хватает errors.New;
  • в тексте есть число, имя или другая ошибка — нужна fmt.Errorf.

Как писать текст

Сообщения ошибок в Go пишут по четырём правилам:

  • текст начинается со строчной буквы;
  • в конце нет точки;
  • сообщение называет, что не получилось и с каким значением: age 200 is too big, а не bad input;
  • в тексте нет слов error или failed: что это ошибка, видно и так, ведь значение пришло в переменную err.

Первые два правила появились потому, что сообщения склеивают. Вызывающий код добавляет перед текстом свой контекст через двоеточие. Из fmt.Errorf("load user: %v", err) получится load user: age is negative, и строка читается как одна фраза. Заглавная буква и точка оказались бы в её середине.

Частая ошибка

Число попытались вставить в текст через errors.New:

GO
err := errors.New("age %d is too big", 200) // ✗
TEXT
too many arguments in call to errors.New

Функция errors.New принимает ровно одну строку и ничего не форматирует: штамп не умеет вписывать числа. Для текста со значениями нужна fmt.Errorf("age %d is too big", n).

Главное

  • errors.New создаёт ошибку с постоянным текстом.
  • fmt.Errorf вставляет значения в текст по правилам Printf и возвращает error.
  • %w в fmt.Errorf сохраняет исходную ошибку внутри новой, а %v — только её текст.
  • Текст ошибки начинается со строчной буквы и не заканчивается точкой.

Подпишись, чтобы пройти курс

Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.

Войти другим способом