Ошибка — это значение
Чему вы научитесь:
- объяснять, почему ошибка в Go — обычное значение, и читать сигнатуры вида
(int, error); - создавать ошибки через
errors.Newиfmt.Errorf; - проверять ошибку сразу после вызова через
if err != nil; - оборачивать ошибку через
%wи находить её черезerrors.Is.

Представьте, что вы заказали посылку. Курьер отдаёт коробку, а вместе с ней накладную. Обычно накладная чистая, но иногда в ней пометка: «товар повреждён» или «не тот размер». Разумно сначала прочитать накладную и только потом открывать коробку.
Функции в Go, которые могут не справиться с работой, устроены так же. Не каждую строку можно превратить в число или в bool. Функция strconv.ParseBool из пакета strconv понимает "true" и "false", а слово "yes" разобрать не может. О неудаче она сообщает вторым результатом — это и есть накладная:
package main
import (
"fmt"
"strconv"
)
func main() {
b, err := strconv.ParseBool("yes")
fmt.Println(b, err) // false strconv.ParseBool: parsing "yes": invalid syntax
b, err = strconv.ParseBool("true")
fmt.Println(b, err) // true <nil>
}Давайте разберём пример по строкам:
- Первый вызов получает
"yes"и возвращаетfalseвместе с ошибкой. - Первый
Printlnпечатает оба результата. Текст ошибки называет функцию, строку и причину:invalid syntax. - Второй вызов разбирает
"true"и возвращаетtrue. - Второй
Printlnпечатаетtrue <nil>: во втором результате<nil>— чистая накладная.
Программа не останавливается и не падает. Ошибка приходит в переменную err, как обычное значение, и программа решает, что с ней делать. Когда разбор удался, в err лежит nil.
Сигнатура с ошибкой
Уже по сигнатуре видно, что функция может не справиться. Сигнатура — это строка с именем функции, её параметрами и типами результатов. У strconv.ParseBool она такая: func ParseBool(str string) (bool, error). Разберём её части:
ParseBool— имя функции;(str string)— параметр: строка, которую нужно разобрать;(bool, error)— два результата: само значение и ошибка.
Второй результат типа error предупреждает вызывающий код ещё до первого запуска. Взять только первый результат нельзя: вызов с одной переменной слева не скомпилируется.
Интерфейс error
Тип error встроен в язык, как int. О нём нужно знать три вещи:
- это интерфейс с одним методом:
Error() string; - метод возвращает текст ошибки, и
fmt.Printlnпечатает именно его; - значением типа
errorможет быть любой тип с таким методом.
Вот свой тип ошибки и функция, которая его возвращает:
type tooOld int
func (a tooOld) Error() string {
return fmt.Sprintf("age %d is too big", int(a))
}
func checkAge(n int) (int, error) {
if n > 150 {
return 0, tooOld(n)
}
return n, nil
}Функция checkAge возвращает значение tooOld(n) там, где ждут error. Компилятор это принимает: у tooOld есть метод Error() string, поэтому тип реализует интерфейс. Так устроены и ошибки стандартной библиотеки: у каждой есть тип с методом Error().
nil — значит успех
Нулевое значение интерфейса — nil. Для ошибки оно значит «всё прошло успешно», поэтому checkAge возвращает n, nil для допустимого возраста:
age, err := checkAge(30)
fmt.Println(age, err) // 30 <nil>
age, err = checkAge(200)
fmt.Println(age, err) // 0 age 200 is too bigРазберём оба вызова:
checkAge(30)проходит проверку и печатает30 <nil>.checkAge(200)возвращает0и ошибку, аfmt.Printlnпечатает её текст:age 200 is too big.
Два правила для результатов
У функций, которые возвращают ошибку, два правила:
- Ошибка стоит последним результатом.
- Если она не
nil, на остальные результаты не полагаются: обычно в них нулевые значения своих типов.
Поэтому при отказе checkAge возвращает 0, а не сам возраст.
Здесь картинка с посылкой перестаёт работать. В жизни из помятой коробки иногда достают что-то целое. В Go при ошибке содержимое коробки не трогают: накладная говорит, что верить ему нельзя.
Сравнение с nil
Значение err сравнивают с nil так же, как указатель:
err == nil— успех;err != nil— неудача.
Текст ошибки для этого читать не нужно. При успехе возвращают именно nil.
Это классический вопрос на собеседовании: если вернуть как error пустой указатель своего типа ошибки, вызывающий код получит err != nil. Как вы уже знаете из темы об интерфейсах, интерфейс с nil-указателем внутри не равен nil.
Частая ошибка
При ошибке вместо числа вернули nil:
func checkAge(n int) (int, error) {
if n > 150 {
return nil, tooOld(n) // ✗
}
return n, nil
}cannot use nil as int value in return statementЗначение nil бывает у указателя, среза, map или интерфейса, но не у числа. Для результата типа int верните его нулевое значение: return 0, tooOld(n).
Главное
- Ошибка в Go — обычное значение типа
error; её возвращают последним результатом. error— интерфейс с одним методомError() string, и его реализует любой тип с таким методом.err == nilзначит успех; приerr != nilна остальные результаты не полагаются.- При успехе возвращают
nil, а при ошибке — нулевые значения остальных результатов, например0.
Подпишись, чтобы пройти курс
Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.