Как %w-обёртывание и errors.Is / errors.As работают вместе в Go?
Дан sentinel-ошибка ErrNotFound и конкретный тип ошибки QueryError (реализующий Unwrap). Реализуйте find так, чтобы вызывающая сторона могла и сопоставить sentinel через errors.Is, и извлечь конкретный *QueryError через errors.As.
Требования:
- добавьте контекст к возвращаемой ошибке, сохранив связь с исходной причиной
- возвращаемая ошибка должна давать успех
errors.Is(err, ErrNotFound) - возвращаемая ошибка должна давать восстановление
*QueryErrorчерезerrors.As(err, &qe)
func find(id string) error {
// ваш код здесь
}
Допишите реализацию.
fmt.Errorf("...: %w", err) оборачивает err, добавляя контекст и сохраняя связь с ним через цепочку Unwrap. errors.Is проходит по цепочке, сопоставляя с sentinel-значением; errors.As проходит по ней, находя ошибку конкретного типа и записывая её в целевой указатель.
- ✗Использовать
%vвместо%w, что теряет связьUnwrap, иerrors.Is/errors.Asбольше не находят совпадение через неё - ✗Сравнивать обёрнутые ошибки через
==вместоerrors.Is— это ломается, как только добавлен контекст - ✗Передавать в
errors.Asне-указатель — это паникует; цель должна быть указателем на тип, реализующий error
- →Почему обёртывание через
%wтребует, чтобы под капотом ошибка реализовывала методUnwrap? - →Когда стоит обернуть несколькими
%wв одном вызовеfmt.Errorf?
Обёртывание и распаковка
package main
import (
"errors"
"fmt"
)
var ErrNotFound = errors.New("not found")
type QueryError struct {
Query string
Err error
}
func (e *QueryError) Error() string { return e.Query + ": " + e.Err.Error() }
func (e *QueryError) Unwrap() error { return e.Err }
func find(id string) error {
// добавляем контекст и сохраняем связь с ErrNotFound через %w
return fmt.Errorf("find %s: %w", id, &QueryError{Query: id, Err: ErrNotFound})
}
func main() {
err := find("u-42")
// errors.Is проходит по цепочке Unwrap до sentinel-значения
fmt.Println(errors.Is(err, ErrNotFound)) // true
// errors.As находит ошибку конкретного типа и записывает её в qe
var qe *QueryError
if errors.As(err, &qe) {
fmt.Println("query was:", qe.Query) // query was: u-42
}
}
%w строит цепочку Unwrap. errors.Is сравнивает каждое звено с целью (через == или метод Is), а errors.As ищет звено нужного типа и присваивает его в указатель &qe. Замена %w на %v оборвала бы цепочку, и оба обхода вернули бы false.