Menu

Errores en Golang: wrap, errors.Is, errors.As y tipos propios

Define errores centinela y tipos de error propios, envuelve errores con %w, compruébalos con errors.Is y errors.As, combina varios con errors.Join y escribe métodos Unwrap e Is cuando los necesites.

Esta página incluye editores ejecutables: edita, ejecuta y ve el resultado al instante.

Tres clases de errores

El código Go usa tres formas de error, de la más simple a la más rica:

  1. Un error improvisado: errors.New("...") o fmt.Errorf("...") creado en el momento. Quien llama solo puede leer el mensaje.
  2. Un error centinela: una variable a nivel de paquete como io.EOF. Quien llama puede comprobarlo con errors.Is.
  3. Un tipo de error: un struct con un método Error() que lleva campos. Quien llama puede extraerlo con errors.As y leer los campos.

Elige la más simple que permita a quien llama hacer lo que necesita.

Errores centinela

Un centinela es un valor de error que se declara una vez y se compara por identidad. Por convención, los nombres empiezan por Err.

Salida:

mug bought
cap is out of stock, notify me later
buy "hat": not found

Dos llamadas a errors.New con el mismo texto producen errores distintos: errors.New("x") == errors.New("x") es false. Por eso un centinela tiene que ser una única variable compartida.

Los centinelas pasan a formar parte de la API de tu paquete. Una vez que quien llama comprueba ErrNotFound, no puedes dejar de devolverlo sin romper su código. Exporta solo los que realmente se necesitan para tomar decisiones.

Tipos de error propios

Cuando quien llama necesita detalles (qué campo, qué código de estado, qué espera antes de reintentar), define un tipo.

Salida:

register: age: must be between 0 and 150
bad field: age

El método tiene un receptor puntero y la función devuelve &ValidationError{...}, así que el destino de errors.As es un *ValidationError y pasas su dirección (&ve, un **ValidationError). Equivocarse en ese nivel es el error clásico con errors.As: con un receptor por valor devolverías ValidationError{...} y declararías var ve ValidationError. go vet detecta el despiste más común, pasar ve en lugar de &ve: second argument to errors.As must be a non-nil pointer to either a type that implements error, or to any interface type.

Envolver con %w

fmt.Errorf con %w devuelve un error que recuerda al que envuelve. Cada capa añade contexto y mantiene la cadena intacta.

Salida:

start: connect db: timeout
  *fmt.wrapError: start: connect db: timeout
  *fmt.wrapError: connect db: timeout
  *errors.errorString: timeout
true
false

Con %v el mensaje es el mismo pero la cadena se corta, así que errors.Is devuelve false. Elige %w cuando quien llama deba poder ver la causa, y %v cuando la causa sea un detalle de implementación del que no quieres que dependa.

Desde Go 1.20, una sola llamada puede envolver varios errores: fmt.Errorf("%w; %w", err1, err2). errors.Is coincide entonces con cualquiera de los dos.

errors.Join: varios errores a la vez

La validación y la limpieza suelen producir más de un error. errors.Join (Go 1.20) los agrupa.

Salida:

name: required
email: required
age: negative
---
true
true

errors.Join devuelve nil cuando todos los argumentos son nil, así que la función anterior no necesita un caso especial para "sin errores".

Métodos Unwrap e Is

errors.Is y errors.As encuentran errores envueltos llamando a un método Unwrap. Un tipo propio que guarda una causa debería exponerla:

Un tipo que envuelve varios errores implementa Unwrap() []error en su lugar.

Un tipo también puede definir Is(target error) bool para decidir la igualdad por sí mismo, por ejemplo para coincidir con cualquier *HTTPError con el mismo código de estado. Rara vez hace falta; envolver un centinela y devolverlo desde Unwrap suele bastar.

La trampa del nil con tipo

Nunca declares una variable de error con tu tipo concreto para devolverla como error:

func check() error {
	var err *ValidationError // nil pointer
	// ... no problem found
	return err // non-nil error! Its type is *ValidationError
}

El if err != nil de quien llama es verdadero, porque una interfaz que contiene un puntero nil con tipo no es nil. Devuelve un nil literal cuando todo va bien, y declara las variables locales de error con el tipo error. La página de interfaces explica por qué.

Qué clase elegir

Quien llama necesitaProporciona
solo registrar o mostrar el fallofmt.Errorf("...: %w", err)
decidir según una condición concretaun centinela var ErrX = errors.New(...)
leer detalles del falloun tipo de error con campos
ver varios fallos independienteserrors.Join

Errores comunes

  • Comparar errores envueltos con ==. Usa errors.Is.
  • Pasar algo que no es un puntero a errors.As. Necesita un puntero a una variable del tipo de destino.
  • Crear un "centinela" dentro de una función. return errors.New("not found") crea un valor nuevo en cada llamada; quien llama no puede compararlo.
  • Exportar todos los errores. Cada centinela o tipo exportado es una promesa de API.
  • Comparar con err.Error(). Los strings son para las personas.

Preguntas frecuentes

¿Cómo creo un error personalizado en Go?

Para una condición fija, declara un centinela a nivel de paquete: var ErrNotFound = errors.New("not found"). Para un error que lleva datos, define un tipo con un método Error() string: type ValidationError struct { Field string } y func (e *ValidationError) Error() string { return e.Field + " is invalid" }.

¿Cómo se envuelve un error en Go?

Usa fmt.Errorf con el verbo %w: return fmt.Errorf("load user %d: %w", id, err). El mensaje del nuevo error incluye el del antiguo, y errors.Unwrap, errors.Is y errors.As pueden llegar al original. Desde Go 1.20 una sola llamada a Errorf puede envolver varios errores con varios verbos %w.

¿Qué diferencia hay entre errors.Is y errors.As?

errors.Is(err, target) responde a "¿está este valor de error concreto en algún punto de la cadena?", para centinelas como io.EOF. errors.As(err, &target) responde a "¿hay un error de este tipo en la cadena?" y, si lo hay, lo guarda en target para que puedas leer sus campos.

¿Qué hace errors.Join?

errors.Join(errs...) (Go 1.20) combina varios errores en uno. Su mensaje son los mensajes individuales separados por saltos de línea, los errores nil se descartan y devuelve nil si todos son nil. errors.Is y errors.As coinciden si coincide cualquiera de los errores combinados.

Coddy programming languages illustration

Aprende a programar con Coddy

COMENZAR