Menu

Erreurs en Golang : wrap, errors.Is, errors.As et types personnalisés

Définissez des erreurs sentinelles et des types d'erreur personnalisés, enveloppez les erreurs avec %w, vérifiez-les avec errors.Is et errors.As, combinez-en plusieurs avec errors.Join, et écrivez des méthodes Unwrap et Is quand vous en avez besoin.

Cette page contient des éditeurs exécutables - modifiez, exécutez et voyez la sortie instantanément.

Trois sortes d'erreurs

Le code Go utilise trois formes d'erreur, de la plus simple à la plus riche :

  1. Une erreur ad hoc : errors.New("...") ou fmt.Errorf("...") créée sur place. Les appelants ne peuvent que lire le message.
  2. Une erreur sentinelle : une variable au niveau du package comme io.EOF. Les appelants peuvent la tester avec errors.Is.
  3. Un type d'erreur : une struct avec une méthode Error(), qui porte des champs. Les appelants peuvent l'extraire avec errors.As et lire les champs.

Choisissez la plus simple qui permet aux appelants de faire ce dont ils ont besoin.

Erreurs sentinelles

Une sentinelle est une valeur d'erreur déclarée une fois et comparée par identité. Par convention, les noms commencent par Err.

Sortie :

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

Deux appels à errors.New avec le même texte produisent des erreurs différentes : errors.New("x") == errors.New("x") vaut false. C'est pour cela qu'une sentinelle doit être une variable unique et partagée.

Les sentinelles font partie de l'API de votre package. Dès que des appelants testent ErrNotFound, vous ne pouvez plus arrêter de la renvoyer sans les casser. N'exportez que celles sur lesquelles les appelants ont réellement besoin de brancher.

Types d'erreur personnalisés

Quand l'appelant a besoin de détails (quel champ, quel code de statut, quel délai avant de réessayer), définissez un type.

Sortie :

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

La méthode a un receveur pointeur et la fonction renvoie &ValidationError{...}, donc la cible de errors.As est un *ValidationError, et vous passez son adresse (&ve, un **ValidationError). Se tromper de niveau est l'erreur classique avec errors.As : avec un receveur valeur, vous renverriez ValidationError{...} et déclareriez var ve ValidationError. go vet détecte l'étourderie la plus courante, passer ve au lieu 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.

Envelopper avec %w

fmt.Errorf avec %w renvoie une erreur qui se souvient de celle qu'elle enveloppe. Chaque couche ajoute du contexte et garde la chaîne intacte.

Sortie :

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

Avec %v, le message est le même mais la chaîne est coupée, donc errors.Is renvoie false. Choisissez %w quand les appelants doivent pouvoir voir la cause, %v quand la cause est un détail d'implémentation dont vous ne voulez pas qu'ils dépendent.

Depuis Go 1.20, un seul appel peut envelopper plusieurs erreurs : fmt.Errorf("%w; %w", err1, err2). errors.Is trouve alors l'une ou l'autre.

errors.Join : plusieurs erreurs à la fois

La validation et le nettoyage produisent souvent plus d'une erreur. errors.Join (Go 1.20) les regroupe.

Sortie :

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

errors.Join renvoie nil quand tous les arguments sont nil, donc la fonction ci-dessus n'a pas besoin de cas particulier pour « aucune erreur ».

Méthodes Unwrap et Is

errors.Is et errors.As trouvent les erreurs enveloppées en appelant une méthode Unwrap. Un type personnalisé qui contient une cause devrait l'exposer :

Un type qui enveloppe plusieurs erreurs implémente plutôt Unwrap() []error.

Un type peut aussi définir Is(target error) bool pour décider lui-même de l'égalité, par exemple pour correspondre à n'importe quel *HTTPError ayant le même code de statut. Vous en avez rarement besoin ; envelopper une sentinelle et la renvoyer depuis Unwrap suffit en général.

Le piège du nil typé

Ne déclarez jamais une variable d'erreur avec votre type concret pour la renvoyer via error :

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

Le if err != nil de l'appelant est vrai, parce qu'une interface contenant un pointeur nil typé n'est pas nil. Renvoyez un nil littéral en cas de succès, et gardez vos variables d'erreur locales typées error. La page sur les interfaces explique pourquoi.

Quelle sorte choisir

Les appelants doiventFournissez
seulement journaliser ou afficher l'échecfmt.Errorf("...: %w", err)
brancher sur une condition préciseune sentinelle var ErrX = errors.New(...)
lire des détails sur l'échecun type d'erreur avec des champs
voir plusieurs échecs indépendantserrors.Join

Erreurs courantes

  • Comparer des erreurs enveloppées avec ==. Utilisez errors.Is.
  • Passer autre chose qu'un pointeur à errors.As. Il faut un pointeur vers une variable du type cible.
  • Créer une « sentinelle » dans une fonction. return errors.New("not found") crée une nouvelle valeur à chaque appel ; les appelants ne peuvent pas s'y comparer.
  • Exporter toutes les erreurs. Chaque sentinelle ou type exporté est une promesse d'API.
  • Comparer sur err.Error(). Les chaînes sont faites pour les humains.

Questions fréquentes

Comment créer une erreur personnalisée en Go ?

Pour une condition fixe, déclarez une sentinelle au niveau du package : var ErrNotFound = errors.New("not found"). Pour une erreur qui transporte des données, définissez un type avec une méthode Error() string : type ValidationError struct { Field string } et func (e *ValidationError) Error() string { return e.Field + " is invalid" }.

Comment envelopper une erreur en Go ?

Utilisez fmt.Errorf avec le verbe %w : return fmt.Errorf("load user %d: %w", id, err). Le message de la nouvelle erreur inclut l'ancien, et errors.Unwrap, errors.Is et errors.As peuvent atteindre l'original. Depuis Go 1.20, un seul appel à Errorf peut envelopper plusieurs erreurs avec plusieurs verbes %w.

Quelle est la différence entre errors.Is et errors.As ?

errors.Is(err, target) répond à « cette valeur d'erreur précise est-elle quelque part dans la chaîne », pour des sentinelles comme io.EOF. errors.As(err, &target) répond à « y a-t-il une erreur de ce type dans la chaîne », et si oui la stocke dans target pour que vous puissiez lire ses champs.

Que fait errors.Join ?

errors.Join(errs...) (Go 1.20) combine plusieurs erreurs en une seule. Son message est constitué des messages individuels séparés par des retours à la ligne, les erreurs nil sont ignorées, et elle renvoie nil si toutes sont nil. errors.Is et errors.As trouvent une correspondance si l'une des erreurs jointes correspond.

Coddy programming languages illustration

Apprendre à coder avec Coddy

COMMENCER