Lección 5 · 5 de 20
Errores propios, Is, As y %w · Go
Cree errores centinela y tipos de error propios en Go, envuélvalos con %w y reconózcalos después con errors.Is y errors.As.

ErrUnknown es un error centinela: una variable del paquete creada con errors.New que representa un fallo concreto, «producto desconocido». Quien la reciba puede preguntar si su error es ese, sin comparar textos.
StockError es un tipo de error propio. Cualquier tipo con el método Error() string cumple la interfaz error, y este, además, lleva datos: cuántas unidades se piden (Want) y cuántas hay (Have). El método está en el puntero, así que el error que se devuelve es un *StockError.
reserve no devuelve esos errores tal cual: los envuelve con fmt.Errorf y el verbo `%w`, que añade contexto (reserve: ...) y guarda el error original dentro. El mensaje queda reserve: want 3, have 1, y el error de dentro sigue disponible. Con %v en lugar de %w solo se copiaría el texto.
`errors.As(err, &se)` recorre la cadena de errores envueltos buscando un *StockError; si lo encuentra, lo guarda en se y devuelve true, y así se puede calcular se.Want - se.Have, 2. `errors.Is(err, ErrUnknown)` recorre la misma cadena buscando ese centinela: true para XX-99, false para un error nil.
switch err := reserve(sku, 3); { declara err solo para ese switch y luego evalúa los case como condiciones. Recuerde: compare errores con errors.Is y no con ==, porque == no ve lo que hay dentro de un error envuelto.
Código · main.go
package mainimport ( "errors" "fmt")var ErrUnknown = errors.New("unknown product") // a sentinel errorvar stock = map[string]int{"KB-01": 5, "MS-02": 1}type StockError struct{ Want, Have int } // a custom error type that carries datafunc (e *StockError) Error() string { return fmt.Sprintf("want %d, have %d", e.Want, e.Have) }func reserve(sku string, qty int) error { if have, ok := stock[sku]; !ok { return fmt.Errorf("reserve: %w", ErrUnknown) // %w wraps the error } else if qty > have { return fmt.Errorf("reserve: %w", &StockError{qty, have}) } return nil}func main() { for _, sku := range []string{"KB-01", "MS-02", "XX-99"} { var se *StockError switch err := reserve(sku, 3); { case errors.As(err, &se): // is there a *StockError in the chain? fmt.Printf("%s: %v (short by %d)\n", sku, err, se.Want-se.Have) default: // errors.Is: is ErrUnknown anywhere in the chain? fmt.Printf("%s: %v (unknown: %t)\n", sku, err, errors.Is(err, ErrUnknown)) } }}Salida · go run main.go
KB-01: <nil> (unknown: false) MS-02: reserve: want 3, have 1 (short by 2) XX-99: reserve: unknown product (unknown: true)
Para recordar
- Un error centinela es una variable creada con
errors.New. - Un tipo con
Error() stringes unerrory puede llevar datos. fmt.Errorf("...: %w", err)añade contexto sin perder el original.errors.Isbusca un valor en la cadena;errors.As, un tipo.- Con errores envueltos,
==no basta: useerrors.Is.
Ejercicio
Cree un centinela ErrInvalidQty y haga que reserve lo devuelva envuelto cuando qty <= 0. Añada un case errors.Is(err, ErrInvalidQty): al switch y pruebe con una cantidad de 0.