Une erreur est une valeur. Et comme toute valeur, on peut l'emboîter dans une autre.An error is a value. And like any value, you can nest it inside another.
Le numéro précédent a posé la règle : en chemin, on n'ajoute que du contexte, on ne traite qu'au sommet. Reste à savoir comment ajouter ce contexte sans rien perdre. Deux verbes s'opposent. Aplatir : fmt.Errorf avec %v transforme l'erreur en texte et la fond dans un message — lisible, mais définitif, l'original n'existe plus. Envelopper : fmt.Errorf avec %w garde l'erreur d'origine vivante, attachée sous la nouvelle, formant une chaîne. Le message affiché est le même ; la différence est invisible à l'écran et décisive dans le code. Une erreur aplatie est une impasse. Une erreur enveloppée est une pile qu'on pourra dérouler, couche après couche, jusqu'à la cause première.The previous issue set the rule: along the way you only add context, you handle only at the top. What remains is how to add that context without losing anything. Two verbs oppose each other. Flatten: fmt.Errorf with %v turns the error into text and melts it into a message — readable, but final, the original no longer exists. Wrap: fmt.Errorf with %w keeps the original error alive, attached beneath the new one, forming a chain. The displayed message is identical; the difference is invisible on screen and decisive in code. A flattened error is a dead end. A wrapped error is a stack you can unroll, layer after layer, down to the first cause.
// %v : on aplatit l'erreur en texte. L'original disparaît dans la chaîne. if err != nil { return fmt.Errorf("load config: %v", err) // %v = juste du texte } // l'appelant lit un beau message… mais ne peut plus retrouver // l'erreur d'origine : os.ErrNotExist s'est dissous dans une string.
// %w : on EMBALLE l'erreur. L'original reste accessible, une couche dessous. if err != nil { return fmt.Errorf("load config: %w", err) // %w = on chaîne } // même message à l'écran, mais l'erreur d'origine est conservée : // errors.Is(err, os.ErrNotExist) la retrouvera au fond de la pile.
Chaque %w ajoute une couche par-dessus la précédente, sans écraser ce qu'il y a dessous. Le résultat est une liste chaînée d'erreurs : tu lis la couche du dessus, mais tu peux descendre jusqu'à la cause. Unwrap() retire une couche ; errors.Is et errors.As descendent jusqu'au bout. La chaîne est une structure de données — et on la parcourt.Each %w adds a layer over the previous one, without crushing what lies beneath. The result is a linked list of errors: you read the top layer, but you can descend to the cause. Unwrap() peels one layer; errors.Is and errors.As go all the way down. The chain is a data structure — and you walk it.
« En montant, j'enveloppe avec %w ; en descendant, je déroule avec Is et As. » Le numéro défend une idée simple : tant que la chaîne n'est pas cassée, n'importe quelle couche peut interroger n'importe quelle couche du dessous. Casser la chaîne — un %v de trop — c'est jeter cette possibilité, exactement comme un _ = jetait l'erreur entière au numéro précédent."On the way up, I wrap with %w; on the way down, I unroll with Is and As." The issue argues one simple thing: as long as the chain isn't broken, any layer can interrogate any layer beneath it. Breaking the chain — one %v too many — throws that possibility away, exactly as a _ = threw the whole error away last issue.
Dans l'usage courant, fmt.Errorf n'a qu'un verbe %w par message : une erreur enveloppe une autre erreur. C'est cohérent avec l'idée de chaîne — une liste a un seul suivant. Depuis Go 1.20, Go sait aussi faire des arbres : fmt.Errorf accepte plusieurs %w, et errors.Join rassemble plusieurs erreurs (toutes les lignes invalides d'un fichier, par exemple) en une seule — errors.Is et errors.As savent les traverser. Mais le cas courant, de loin, reste la chaîne simple : une cause, du contexte ajouté à chaque étage, une couche par return.In everyday use, fmt.Errorf has a single %w verb per message: one error wraps one other error. That fits the chain idea — a list has a single next. Since Go 1.20, Go can build trees too: fmt.Errorf accepts several %w, and errors.Join gathers several errors (every invalid line of a file, say) into one — errors.Is and errors.As can traverse them. But the common case, by far, stays the simple chain: one cause, context added at each floor, one layer per return.