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

Newtype & type-state, ou l'état illégal qui ne compile pas Newtype & type-state, or the illegal state that won't compile

Un Connection<Open> et un Connection<Closed> qui ne sont pas le même type : send() n'existe simplement pas sur une connexion fermée. Contre-exemple : la cérémonie qui coûte plus que le bug. A Connection<Open> and a Connection<Closed> that aren't the same type: send() simply doesn't exist on a closed connection. Counter-example: the ceremony that costs more than the bug.

AudienceAudience
Dev maîtrisant enums et Option (Vol 3 №1–2) Dev fluent in enums and Option (Vol 3 №1–2)
Format
Self-paced
ChapitresChapters
5
Date
Juin 2026 Jun 2026
≈ 18 min ●●●○ TypesType-stateInvariants

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

Tes propres invariants, déplacés de ta tête vers le type.Your own invariants, moved from your head into the type.

Option a montré le mouvement : prendre une règle qui vivait dans ta tête — « ça peut être absent » — et la déplacer dans le type, où le compilateur la fait respecter. Ce numéro généralise le geste à tes propres règles. Deux outils : le newtype, qui donne un sens et une garantie à un type primitif ; et le type-state, qui encode l'état d'un objet dans son type, pour que les opérations invalides n'existent tout simplement pas. La fin du Vol 3 : rendre l'illégal non pas détectable, mais impossible à écrire — dans ton domaine, pas seulement dans la bibliothèque standard.Option showed the move: take a rule that lived in your head — 'this can be absent' — and move it into the type, where the compiler enforces it. This issue generalizes the gesture to your own rules. Two tools: the newtype, which gives meaning and a guarantee to a primitive type; and type-state, which encodes an object's state into its type, so invalid operations simply don't exist. The close of Vol 3: making the illegal not detectable but impossible to write — in your domain, not just the standard library.

Le newtype — un primitif, mais avec un sens et une garantieThe newtype — a primitive, but with meaning and a guarantee
struct Email(String);          // un newtype : un String, mais PAS un String

impl Email {
    // le SEUL chemin de construction passe par la validation
    fn parse(s: String) -> Option<Email> {
        if s.contains('@') { Some(Email(s)) } else { None }
    }
}

// dès lors, un Email qui existe est un email validé — par construction.
// et on ne peut plus le confondre avec un mot de passe, lui aussi String.
« Parse, don't validate »'Parse, don't validate'

L'idée tient en une formule : ne valide pas une donnée pour la laisser ensuite circuler sous son type brut — transforme-la en un type qui prouve sa validité. Un String validé reste un String : trois fonctions plus loin, plus rien ne dit qu'il l'a été. Un Email issu de parse porte sa garantie partout où il va — la validation a eu lieu une fois, à la frontière, et le type s'en souvient pour toujours.The idea fits in a phrase: don't validate data only to let it flow on under its raw type — transform it into a type that proves its validity. A validated String is still a String: three functions later, nothing says it ever was. An Email from parse carries its guarantee everywhere it goes — validation happened once, at the boundary, and the type remembers it forever.

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

« Cette règle, je la vérifie à chaque usage, ou je l'encode une fois dans un type ? » Si une donnée a un invariant — validée, normalisée, dans une certaine unité, dans un certain état — un type dédié le porte une bonne fois. Le compilateur cesse alors de te faire confiance pour t'en souvenir, et le fait à ta place, à chaque appel.'This rule — do I check it at every use, or encode it once in a type?' If a piece of data has an invariant — validated, normalized, in a certain unit, in a certain state — a dedicated type carries it once and for all. The compiler then stops trusting you to remember it, and does so for you, at every call.

Coût à l'exécution : zéro.Runtime cost: zero.

Un newtype comme Email(String) ne coûte rien à l'exécution : c'est exactement la représentation mémoire d'un String, l'enveloppe disparaît à la compilation. On parle d'abstraction « à coût nul » — un thème qui traversera tout le reste de la série. La sécurité de type est une affaire de compilation, pas de cycles CPU.A newtype like Email(String) costs nothing at runtime: it has exactly a String's memory representation, the wrapper vanishes at compile time. This is 'zero-cost' abstraction — a theme running through the rest of the series. Type safety is a compile-time matter, not CPU cycles.

🔒

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