
En Go, une erreur est une valeur ordinaire qu'on renvoie et qu'on vérifie, pas une exception qu'on attrape. Ce style explicite est déroutant au début, mais c'est lui qui rend le flot d'erreurs visible et traçable. Ce guide vous apprend à renvoyer une erreur, à l'envelopper pour garder son contexte, et à réagir précisément avec errors.Is et errors.As. Public visé : vous connaissez les fonctions et les interfaces. Exemples testés sur Go 1.26.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Renvoyer et vérifier une erreur explicitement.
- Envelopper une erreur avec
fmt.Errorfet%w. - Comparer avec
errors.Iset extraire avecerrors.As. - Définir une erreur sentinelle et une erreur personnalisée.
error est une valeur : la convention de retour
Section intitulée « error est une valeur : la convention de retour »error est une interface toute simple (un type qui a une méthode Error() string). La convention Go : une fonction qui peut échouer renvoie une erreur en dernier retour, et l'appelant la vérifie immédiatement.
f, err := os.Open("config.yaml")if err != nil { return err // on remonte l'erreur}defer f.Close()Le motif if err != nil revient partout : c'est le prix de l'explicite. La règle d'or : ne jamais ignorer une erreur silencieusement. Un _ = faire() qui jette l'erreur est un bug en puissance.
Envelopper une erreur avec %w
Section intitulée « Envelopper une erreur avec %w »Remonter une erreur brute perd le contexte : on sait qu'il y a eu une erreur, pas où ni pourquoi. fmt.Errorf avec le verbe %w enveloppe (wrap) l'erreur d'origine en ajoutant du contexte, tout en la gardant récupérable.
var ErrIntrouvable = errors.New("ressource introuvable") // erreur sentinelle
func charger(id int) error { if id == 0 { return fmt.Errorf("charger id=%d: %w", id, ErrIntrouvable) } return nil}Le message final devient charger id=0: ressource introuvable : on cumule le contexte de chaque niveau. Le %w (et non %v) est essentiel : il conserve un lien vers l'erreur enveloppée, exploitable par errors.Is.
Comparer avec errors.Is, extraire avec errors.As
Section intitulée « Comparer avec errors.Is, extraire avec errors.As »Une fois les erreurs enveloppées, on veut réagir à une cause précise. Deux outils : errors.Is compare à une erreur cible ; errors.As extrait un type d'erreur concret, même à travers plusieurs couches de wrapping.
type ErrValidation struct{ Champ string }func (e *ErrValidation) Error() string { return "champ invalide: " + e.Champ }
func main() { err := charger(0) fmt.Println(errors.Is(err, ErrIntrouvable)) // true, même enveloppée
err2 := fmt.Errorf("wrap: %w", &ErrValidation{Champ: "id"}) var ev *ErrValidation if errors.As(err2, &ev) { // récupère le type concret fmt.Println("champ en cause:", ev.Champ) // id }}errors.Is(err, ErrIntrouvable) renvoie true même si l'erreur a été enveloppée plusieurs fois : il déroule la chaîne. errors.As fait de même mais remplit une variable du type cherché, pour accéder à ses champs (ev.Champ). C'est bien plus robuste que comparer des messages texte.
- Une erreur sentinelle (
var ErrX = errors.New(...)) suffit quand vous voulez juste distinguer un cas (introuvable, expiré). On la teste avecerrors.Is. - Une erreur personnalisée (un type avec une méthode
Error()) sert quand l'erreur porte des données (le champ fautif, un code HTTP). On l'extrait avecerrors.As.
Choisissez la sentinelle par défaut, et le type personnalisé dès que l'appelant a besoin d'informations structurées sur l'erreur.
Exercice : rejeter une entrée vide
Section intitulée « Exercice : rejeter une entrée vide »Un exercice de wrapping et de test d'erreur. Cherchez la solution avant d'ouvrir la réponse.
Écrivez une fonction normaliser(s string) (string, error) qui :
- renvoie une erreur enveloppant une sentinelle
ErrVidesi la chaîne est vide (ou uniquement des espaces). - sinon renvoie la chaîne en minuscules et sans espaces autour.
Dans main, appelez-la avec " " et réagissez au cas vide avec errors.Is, puis avec " Bonjour " pour vérifier la normalisation.
Indice : strings.TrimSpace retire les espaces ; %w conserve le lien vers ErrVide.
La sentinelle ErrVide est enveloppée avec %w, ce qui permet à l'appelant de la reconnaître avec errors.Is.
package main
import ( "errors" "fmt" "strings")
var ErrVide = errors.New("entrée vide")
func normaliser(s string) (string, error) { if strings.TrimSpace(s) == "" { return "", fmt.Errorf("normaliser: %w", ErrVide) } return strings.ToLower(strings.TrimSpace(s)), nil}
func main() { _, err := normaliser(" ") if errors.Is(err, ErrVide) { fmt.Println("rejeté : l'entrée est vide") } v, _ := normaliser(" Bonjour ") fmt.Printf("normalisé: %q\n", v)}Résultat :
rejeté : l'entrée est videnormalisé: "bonjour"L'appelant n'a pas à comparer le message texte : il teste errors.Is(err, ErrVide), robuste même si un niveau intermédiaire ajoute du contexte. C'est la façon idiomatique de piloter le comportement à partir d'une erreur.
À retenir
Section intitulée « À retenir »- En Go, une erreur est une valeur renvoyée en dernier et vérifiée avec
if err != nil; ne l'ignorez jamais en silence. fmt.Errorf("... %w", err)enveloppe une erreur en ajoutant du contexte tout en gardant le lien.errors.Iscompare à une erreur cible à travers le wrapping ;errors.Asextrait un type d'erreur concret.- Une erreur sentinelle distingue un cas ; une erreur personnalisée porte des données structurées.
- Piloter le code par
errors.Is/Asest bien plus robuste que comparer des messages texte.