Une collection éditoriale pour le développeur qui vient d'un langage à GC et ne veut pas grinder la syntaxe. Un numéro = un mécanisme-clé, disséqué jusqu'à ce que la tension soit nue : quel bug il rend impossible, ce qu'il coûte, et quand ne pas l'employer. On ne se bat plus contre le compilateur — on l'écoute énoncer un théorème. An editorial collection for the developer coming from a GC language who refuses to grind syntax. One issue = one key mechanism, dissected until the tension is bare: which bug it makes impossible, what it costs, and when not to use it. You stop fighting the compiler — you hear it state a theorem.
Le code naïf croit copier : let b = a. En Rust, la possession se transfère — a ne possède plus rien, et le réutiliser ne compile pas. Trois lignes, et tout le modèle de Rust tient dedans : un propriétaire, un drop, zéro double-free. Naive code thinks it copies: let b = a. In Rust, ownership transfers — a owns nothing anymore, and reusing it won't compile. Three lines, and all of Rust's model fits inside: one owner, one drop, zero double-free.
Emprunter (& / &mut) au lieu de céder : regarder une donnée sans la posséder. Et la règle d'or — un seul &mut, ou autant de & qu'on veut, jamais les deux — qui ne contraint pas le langage mais retourne la définition même d'une course de données en interdiction. Borrow (& / &mut) instead of handing over: look at data without owning it. And the golden rule — one &mut, or as many & as you like, never both — which doesn't constrain the language but flips the very definition of a data race into a prohibition.
&str, &[T] : une fenêtre sur une donnée, sans allocation ni possession. Le réflexe qui désamorce 80 % des combats futurs : prends &str en paramètre là où tu prenais String. &str, &[T]: a window onto data, with no allocation and no ownership. The reflex that defuses 80% of future fights: take &str where you were taking String.
L'annotation ne rallonge aucune vie : elle relie des durées pour que le compilateur prouve qu'aucune référence ne survit à sa donnée. On part du use-after-free que Rust refuse, pas de la syntaxe. The annotation lengthens no life: it links durations so the compiler can prove no reference outlives its data. We start from the use-after-free Rust refuses, not from syntax.
Les trois règles que le compilateur applique seul pour deviner les durées de vie — et pourquoi on n'annote que lorsqu'il reste une vraie ambiguïté à lever. The three rules the compiler applies on its own to infer lifetimes — and why you annotate only when a genuine ambiguity remains.
Face à une erreur de lifetime, le débutant annote plus fort jusqu'à abandonner. Le bon réflexe : si les lifetimes deviennent ingérables, c'est le design de possession qui est faux. Facing a lifetime error, the beginner annotates harder until they give up. The right reflex: if lifetimes become unmanageable, it's the ownership design that's wrong.
Modéliser l'une de ces formes, exclusivement — et l'exhaustivité que match impose : le compilateur refuse d'oublier un cas. L'état invalide devient irreprésentable. Model one of these shapes, exclusively — and the exhaustiveness match enforces: the compiler refuses to forget a case. The invalid state becomes unrepresentable.
Le milliard de dollars qu'on ne paie pas : l'absence devient un cas qu'on DOIT traiter. unwrap et expect ont un prix — on le nomme. The billion-dollar mistake you don't pay: absence becomes a case you MUST handle. unwrap and expect have a price — we name it.
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.
L'erreur est dans le type de retour, pas dans un canal parallèle invisible comme l'exception. La distinction qui structure tout : erreur attendue vs bug. The error lives in the return type, not in an invisible parallel channel like the exception. The distinction that structures everything: expected error vs bug.
Un type d'erreur qui dit ce qui a échoué — et le choix bibliothèque vs application : enum d'erreur précis et From pour une lib, contexte agrégé pour une app. An error type that says what failed — and the library vs application choice: a precise error enum and From for a lib, aggregated context for an app.
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.
La monomorphisation : zéro coût à l'exécution, mais du code dupliqué à la compilation. Rust n'a pas d'héritage — et c'est un choix, pas un manque. Monomorphization: zero runtime cost, but duplicated code at compile time. Rust has no inheritance — and that's a choice, not a lack.
Une vtable, un coût d'indirection, échangé contre la flexibilité : mettre des types hétérogènes dans le même Vec. Contre-exemple : Box<dyn> réflexe alors qu'un generic suffisait. A vtable, an indirection cost, traded for flexibility: putting heterogeneous types in the same Vec. Counter-example: reflexive Box<dyn> when a generic would do.
Pourquoi on ne peut pas tout implémenter partout : la cohérence. Et les patterns autour — le newtype pour contourner l'orphan rule proprement. Why you can't implement everything everywhere: coherence. And the patterns around it — the newtype to work around the orphan rule cleanly.
Taille connue, types récursifs, possession unique d'une donnée déportée sur le tas. La première échappatoire à l'arbre de possession — la plus simple, la moins chère. Known size, recursive types, single ownership of data relocated to the heap. The first escape from the ownership tree — the simplest, the cheapest.
Plusieurs propriétaires par comptage de références — et les cycles qui fuient : la fuite mémoire que Rust n'empêche pas. Weak comme issue. Multiple owners by reference counting — and the cycles that leak: the memory leak Rust does not prevent. Weak as the way out.
Muter à travers un & partagé, au risque d'un panic à l'exécution. Le réflexe paresseux Rc<RefCell> qui réintroduit les bugs que le Vol 1 avait éliminés — un Vec + indices aurait suffi. Mutate through a shared &, at the risk of a runtime panic. The lazy Rc<RefCell> reflex that reintroduces the bugs Vol 1 eliminated — a Vec + indices would have sufficed.
Le move qui dit cette donnée part avec toi : transférer la possession dans un thread. La même règle aliasing XOR mutation, appliquée aux threads — et le thread scopé qui prête au lieu de déplacer. The move that says this data leaves with you: transferring ownership into a thread. The same aliasing XOR mutation rule, applied to threads — and the scoped thread that lends instead of moving.
Arc pour partager la possession entre threads, Mutex pour rétablir l'exclusivité — ni l'un ni l'autre ne suffit seul. Et le rappel honnête : Rust empêche le data race, pas le deadlock. Arc to share ownership across threads, Mutex to restore exclusivity — neither alone is enough. And the honest reminder: Rust prevents the data race, not the deadlock.
Passer des messages plutôt que partager de la mémoire : la possession transférée par le canal. Plus rien à partager, donc plus de course — par construction, pas par verrou. Pass messages rather than share memory: ownership transferred through the channel. Nothing left to share, hence no race — by construction, not by lock.
Un Future décrit un calcul, il ne le lance pas : non-await, il est inerte. Casser l'analogie avec la promesse JS qui part dès sa création — un bug silencieux fréquent. A Future describes a computation, it doesn't start it: un-awaited, it is inert. Breaking the analogy with the JS promise that fires on creation — a frequent silent bug.
async contamine la signature de tout ce qui l'appelle — et la donnée tenue à travers un .await doit être Send. Le Vol 7 ressurgit en plein async. async contaminates the signature of everything that calls it — and data held across a .await must be Send. Vol 7 resurfaces mid-async.
.await est un point d'annulation : select! garde le premier prêt et lâche les autres, drop annule une tâche. La cancellation safety qu'on oublie jusqu'à ce qu'elle morde. .await is a cancellation point: select! keeps the first ready and drops the rest, drop cancels a task. The cancellation safety you forget until it bites.
La chaîne .iter().map().filter().collect() compile vers la même boucle qu'à la main — lisibilité sans surcoût, le zero-cost prouvé plutôt que promis. The .iter().map().filter().collect() chain compiles to the same loop as by hand — readability without overhead, zero-cost proven rather than promised.
Le .clone() pour faire taire le compilateur cache une allocation — souvent un emprunt qu'on n'a pas su écrire. Cow n'alloue que si l'on mute vraiment. The .clone() to silence the compiler hides an allocation — often a borrow you couldn't express. Cow allocates only if you actually mutate.
unsafe ne désactive pas les règles de Rust : il transfère leur preuve du compilateur vers toi. Le dernier recours, encapsulé derrière une API sûre — pas le réflexe pour contourner un combat de borrow checker. unsafe does not disable Rust's rules: it transfers their proof from the compiler to you. The last resort, encapsulated behind a safe API — not the reflex to dodge a borrow checker fight.
Le piège, en apprenant Rust, c'est de se battre contre le compilateur — de mémoriser les incantations qui « font passer le borrow checker » sans comprendre ce qu'il prouve. Chaque numéro dissèque une seule tension : possession, durée de vie, type, coût. Le bug qu'on n'aura pas d'abord, le prix ensuite, et le contre-exemple — quand ne pas l'employer. The trap, learning Rust, is fighting the compiler — memorizing the incantations that 'make the borrow checker pass' without grasping what it proves. Each issue dissects a single tension: ownership, lifetime, type, cost. The bug you won't have first, the price next, and the counter-example — when not to use it.
La règle tacite : en Rust, on ne débugge pas à l'exécution ce qu'on peut rendre impossible à la compilation. Le compilateur n'est pas un gardien tatillon ; c'est un système de preuve, et chacun de ses refus est un théorème qu'on n'a pas encore su énoncer. The unspoken rule: in Rust, you don't debug at runtime what you can make impossible at compile time. The compiler isn't a fussy gatekeeper; it's a proof system, and every refusal is a theorem you haven't yet learned to state.