Un atelier Rust · Édition d'ApprentissageA Rust Workshop · Learning Edition

panic, unwrap & les invariants panic, unwrap & invariants

Un invariant brisé est un bug, pas une condition métier — et là, paniquer est correct. Contre-exemple : le unwrap partout pour faire passer le compilateur, l'exception non gérée qui revient. A broken invariant is a bug, not a business condition — and there, panicking is correct. Counter-example: unwrap everywhere to make the compiler pass, the unhandled exception returning.

AudienceAudience
Dev maîtrisant Result, ? et la conception d'erreurs (Vol 4 №1–2) Dev fluent in Result, ? and error design (Vol 4 №1–2)
Format
Self-paced
ChapitresChapters
5
Date
Juin 2026 Jun 2026
≈ 18 min ●●●○ ErreurspanicInvariants

Chapitre 1 en accès libre — la suite (ch. 2 à 5) est réservée. Chapter 1 free to read — the rest (ch. 2–5) is members-only.

01CadrageFraming3 min

Toutes les erreurs ne se gèrent pas. Certaines doivent tout arrêter.Not every error is to be handled. Some must stop everything.

Deux numéros ont martelé : ne panique pas, propage. On pourrait en conclure qu'un programme bien écrit ne panique jamais — ce serait l'erreur inverse du unwrap partout, mais une erreur quand même. La distinction qui ouvrait le volume revient ici pour le clore : une erreur attendue (fichier absent, réseau coupé, saisie invalide) appartient au Result ; un bug — un invariant qu'on s'était promis et qui se rompt — n'a rien à y faire. Quand un état déclaré impossible survient, continuer en faisant semblant est pire que s'arrêter : on propagerait des données corrompues. Là, paniquer n'est pas un échec de conception, c'est la conception.Two issues hammered: don't panic, propagate. You might conclude a well-written program never panics — that would be the mirror of unwrap-everywhere, but a mistake all the same. The distinction that opened the volume returns to close it: an expected error (missing file, dropped network, invalid input) belongs in a Result; a bug — an invariant you promised yourself that breaks — has no place there. When a state declared impossible occurs, carrying on pretending is worse than stopping: you'd propagate corrupted data. There, panicking isn't a design failure, it's the design.

La frontière — erreur attendue vs invariant briséThe border — expected error vs broken invariant
// ERREUR ATTENDUE — le monde réel, prévisible → Result
fn lire(chemin: &str) -> Result<String, io::Error> { /* le fichier peut manquer */ }

// BUG — un invariant que MON code s'était promis → panic
fn moyenne(notes: &[f64]) -> f64 {
    assert!(!notes.is_empty(), "moyenne() appelée sur une tranche vide");
    notes.iter().sum::<f64>() / notes.len() as f64   // len() == 0 serait une faute
}

// la frontière : « le monde extérieur peut-il produire ça normalement ? »
//   oui → Result.   non, c'est MA promesse qui se rompt → panic.
cette condition survient…this condition arises…
le monde extérieur la produit normalementthe outside world produces it normally
attenduexpected
Result<T, E>
un invariant à MOI se romptan invariant of MINE breaks
bugbug
panic! / assert!
Paniquer, c'est échouer fort et tôtTo panic is to fail loud and early

Un panic! déroule la pile (les destructeurs s'exécutent, la mémoire est libérée proprement) puis termine le thread — et, par défaut, le programme. Ce n'est pas un crash sauvage à la « segfault » : c'est un arrêt contrôlé, avec un message et, en debug, une trace. Tout l'enjeu est on le déclenche. Paniquer au plus près de l'invariant rompu, c'est attraper le bug à sa source, avant qu'il ne contamine le reste. Le masquer en Result et le propager, c'est laisser le programme avancer sur une donnée déjà fausse — l'arrêt n'arrivera que plus loin, là où la cause est devenue illisible.A panic! unwinds the stack (destructors run, memory is freed cleanly) then ends the thread — and, by default, the program. It's not a wild 'segfault'-style crash: it's a controlled stop, with a message and, in debug, a trace. The whole question is where you trigger it. Panicking as close as possible to the broken invariant catches the bug at its source, before it contaminates the rest. Masking it as a Result and propagating it lets the program move on with already-wrong data — the stop comes only later, where the cause has become unreadable.

Le réflexe du numéroThe issue's reflex

« Si cette condition survient, est-ce une situation que le monde produit normalement, ou la preuve que mon code a un bug ? » Normale → Result, on la gère. Bug → panic / assert, on s'arrête. Le critère n'est pas la gravité, mais l'origine : un disque plein est grave et reste un Result ; un index négatif est « anodin » et reste un bug.'If this condition arises, is it a situation the world produces normally, or proof my code has a bug?' Normal → Result, handle it. Bug → panic / assert, stop. The criterion isn't severity, but origin: a full disk is severe and stays a Result; a negative index is 'trivial' and stays a bug.

Un invariant, c'est une promesse.An invariant is a promise.

« Cette tranche n'est jamais vide ici », « cet index est toujours valide », « on n'atteint jamais cette branche ». Ce sont des promesses que ton code se fait à lui-même, qu'aucune entrée externe ne peut violer si le code est correct. Les rompre signifie donc une seule chose : le code est faux. Paniquer y est la réponse honnête — mieux vaut un arrêt net qu'un programme qui continue sur une hypothèse déjà fausse.'This slice is never empty here', 'this index is always valid', 'we never reach this branch'. These are promises your code makes to itself, that no external input can violate if the code is correct. Breaking them therefore means one thing: the code is wrong. Panicking is the honest answer — a clean stop beats a program running on an already-false assumption.

🔒

La suite est réservée The rest is members-only

Le premier numéro est libre. Débloque tout The Rust Loop — tous les volumes, à vie — pour 5 €, paiement unique. The first issue is free. Unlock all of The Rust Loop — every volume, forever — for €5, one-time.

Retour au kiosqueBack to newsstand