Перейти к содержанию

Ошибки, defer, panic, recover

error

В Go ошибка — это значение, реализующее интерфейс:

type error interface {
    Error() string
}

Каноничная сигнатура: func Foo() (Result, error).

result, err := Foo()
if err != nil {
    return nil, fmt.Errorf("foo: %w", err)
}

%w — wrapping, нужен для errors.Is / errors.As:

if errors.Is(err, sql.ErrNoRows) { ... }
var apiErr *APIError
if errors.As(err, &apiErr) { ... }

Sentinel и typed errors

// Sentinel — заранее объявленное значение
var ErrNotFound = errors.New("not found")

// Typed error — структура с дополнительным контекстом
type ValidationError struct {
    Field string
    Msg   string
}
func (e *ValidationError) Error() string { return e.Field + ": " + e.Msg }

Sentinel сравниваем через errors.Is, typed — через errors.As.

defer

file, err := os.Open(path)
if err != nil { return err }
defer file.Close()   // выполнится при выходе из функции

Свойства defer:

  1. LIFO порядок (стек).
  2. Аргументы вычисляются в момент defer, тело — в момент выхода.
  3. Гарантированно выполняется при panic'е (что даёт recover).

defer-гочча: аргументы вычисляются сразу

i := 0
defer fmt.Println(i)   // напечатает 0
i++

Если хочешь актуальное значение — используй замыкание:

defer func() { fmt.Println(i) }()   // напечатает 1

panic / recover

panic останавливает выполнение и раскручивает стек. recover ловит её только внутри deferred функции:

func safeDivide(a, b int) (result int, err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("panic: %v", r)
        }
    }()
    return a / b, nil   // panic при b==0
}

Когда уместно использовать panic: - programmer error (assertion, неконсистентное состояние); - невосстановимое состояние (отказ инициализации).

Для бизнес-ошибок — всегда error. panic в публичном API — плохой тон.

📖 Связанные задачи: must-solve-100 → C-006..C-008.