L'union pose les formes. Mais rien ne t'oblige à toutes les traiter.The union lays down the shapes. But nothing forces you to handle them all.
C'est la porte laissée ouverte par le numéro précédent. Une union discriminée décrit exactement les formes légales — mais le switch qui les parcourt peut en oublier une, et compiler quand même. Pire : le jour où tu ajoutes un membre, tous les switch déjà écrits l'ignorent en silence. Le bug n'est pas à l'écriture du switch ; il naît six mois plus tard, à l'ajout d'une variante. never est l'outil qui transforme cet oubli en erreur de compilation.This is the door left open by the previous issue. A discriminated union describes exactly the legal shapes — but the switch that walks them can forget one, and still compile. Worse: the day you add a member, every switch already written ignores it silently. The bug isn't in writing the switch; it's born six months later, when a variant is added. never is the tool that turns that omission into a compile error.
type Shape = | { kind: "circle"; r: number } | { kind: "rect"; w: number; h: number } | { kind: "triangle"; b: number; h: number }; // ← ajouté aujourd'hui function area(s: Shape) { switch (s.kind) { case "circle": return Math.PI * s.r ** 2; case "rect": return s.w * s.h; // triangle oublié : aucune erreur ICI — area est inférée number | undefined } } // 💥 l'erreur surgit loin, à l'usage — ou jamais : `${area(t)} m²` affiche « undefined m² »
Sans garde, le compilateur te laisse oublier une branche — et le coût se paie à l'exécution, loin de la cause. Avec never, l'oubli devient un refus de compiler, pointé sur la variante non gérée.Without a guard, the compiler lets you forget a branch — and the cost is paid at runtime, far from the cause. With never, the omission becomes a refusal to compile, pointed at the unhandled variant.
function area(s: Shape): number { switch (s.kind) { case "circle": return Math.PI * s.r ** 2; case "rect": return s.w * s.h; case "triangle": return (s.b * s.h) / 2; default: { const _exhaustive: never = s; // ✗ si un cas manque, s n'est PAS 'never' return _exhaustive; // → erreur de compilation, pointée sur le cas oublié } } }
« Si j'ajoute un membre à cette union, le compilateur me force-t-il à traiter le nouveau cas ? » Un switch sur un tag sans garde d'exhaustivité est une bombe à retardement — branche le default sur never et l'oubli devient impossible."If I add a member to this union, does the compiler force me to handle the new case?" A switch on a tag without an exhaustiveness guard is a time bomb — wire the default to never and the omission becomes impossible.
Le 3.1 rendait l'état illégal irreprésentable ; le 3.2 rend l'oubli d'un cas irréalisable. Les deux ensemble forment le cœur du volume : on conçoit les formes, puis on exige que chacune soit traitée — et le compilateur tient le registre à notre place.3.1 made the illegal state unrepresentable; 3.2 makes forgetting a case impossible. Together they form the heart of the volume: you design the shapes, then demand that each be handled — and the compiler keeps the ledger for you.