DMarlon Studio

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.

Go · Errores propios, Is, As y %w en DMarlon Studio: el código del ejemplo y su salida en la terminal

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() string es un error y puede llevar datos.
  • fmt.Errorf("...: %w", err) añade contexto sin perder el original.
  • errors.Is busca un valor en la cadena; errors.As, un tipo.
  • Con errores envueltos, == no basta: use errors.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.