Ошибки и defer

Тип error: возврат, проверка и оборачивание

Содержание курса

Проверка ошибки через if err != nil и функция validatePositive

После вызова функции, возвращающей error, принято проверять результат немедленно и завершать ветку с ошибкой явно — через return, return err или передачу выше:

result, err := someFunc()
if err != nil {
    return err // или log + return, или return 0, err
}
// только здесь result можно использовать согласно контракту функции

Ключ — в завершении ветки. Если после if err != nil { ... } нет return или другого выхода, выполнение продолжится с тем же result. Что именно содержит result при ошибке — зависит от контракта конкретной функции: одни возвращают нулевое значение, другие — частичный результат (например, io.Reader.Read сообщает, сколько байт успело прочитаться до сбоя). Считать result пригодным без явного разрешения контракта нельзя.

Соберём полный пример:

package main

import (
    "errors"
    "fmt"
)

var ErrNotPositive = errors.New("число должно быть положительным")

func validatePositive(n int) (int, error) {
    if n <= 0 {
        return 0, ErrNotPositive
    }
    return n, nil
}

func process(n int) (int, error) {
    result, err := validatePositive(n)
    if err != nil {
        return 0, fmt.Errorf("process: %w", err)
    }
    return result, nil
}

func main() {
    var n int
    fmt.Scan(&n)

    result, err := process(n)
    if err != nil {
        fmt.Println("ошибка:", err)
        if errors.Is(err, ErrNotPositive) {
            fmt.Println("причина: число не положительное")
        }
        return
    }
    fmt.Println("результат:", result)
}

Запустим с вводом -3:

ошибка: process: число должно быть положительным
причина: число не положительное

Несколько деталей, на которые стоит обратить внимание.

validatePositive и process возвращают 0 в позиции int при ошибке — не потому что 0 что-то означает, а просто как неиспользуемое значение: такой контракт выбран явно. Именно поэтому process, увидев err != nil от validatePositive, немедленно возвращает 0, fmt.Errorf(...) — значение результата отбрасывается, ошибка с контекстом поднимается выше.

errors.Is внутри main находит исходный ErrNotPositive даже сквозь обёртку "process: " — этот механизм разобран в предыдущей секции, здесь мы видим его в действии целиком.

Теперь о том, что происходит при пропуске проверки:

result, _ := process(-3) // ошибка выброшена
fmt.Println(result)      // печатает 0 — но это нулевое значение по контракту process при сбое, не результат вычисления

Компилятор не запретит такой код. result равен 0 не потому что вычисление дало ноль, а потому что произошёл сбой — и process намеренно вернула 0 по своему контракту. Никакого предупреждения нет — ошибка растворилась. Поэтому _ для ошибки допустим только когда вы точно знаете, что сбой в данном месте невозможен или заведомо несущественен.