Vol. 1 · № 02 · Un atelier RustA Rust workshop

Borrowing, ou la règle aliasing XOR mutation Borrowing, or the aliasing XOR mutation rule

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.

AudienceAudience
Dev venant d'un langage à GC, prêt à raisonner possession Dev from a GC language, ready to reason about ownership
Format
Self-paced
ChapitresChapters
5
Date
Juin 2026 Jun 2026
≈ 22 min ●○○○ PossessionEmpruntAliasing
SommaireContents ·
№ 01CadrageFraming3 min

Prêter une donnée. La regarder sans la posséder.Lend a value. Look at it without owning it.

Le numéro précédent finissait sur une promesse : avant de cloner, demande-toi si tu voulais seulement lire. Voici la réponse. Un emprunt — une référence, le & — donne accès à une donnée sans en transférer la possession. La fonction lit, le propriétaire d'origine garde tout. Pas de move, pas de clone, pas d'allocation.The previous issue ended on a promise: before cloning, ask whether you only meant to read. Here's the answer. A borrow — a reference, the & — gives access to a value without transferring its ownership. The function reads, the original owner keeps everything. No move, no clone, no allocation.

L'emprunt partagé — & : je lis, je ne possède pasThe shared borrow — &: I read, I don't own
fn longueur(s: &String) -> usize {   // & : on EMPRUNTE, on ne possède pas
    s.len()
}                                     // s sort de portée, mais ne droppe rien

let nom = String::from("Ada");
let n = longueur(&nom);               // on prête nom le temps de l'appel
println!("{nom} fait {n} lettres");   // nom est toujours à nous
Qui possède, qui emprunte ?Who owns, who borrows?
nom
possède "Ada"owns "Ada"
⇠
pointe vers, ne possède paspoints to, doesn't own
&nom
emprunt · lecture seuleborrow · read-only

La référence &nom pointe vers la donnée mais ne la possède pas : quand elle disparaît, rien n'est libéré. nom reste l'unique propriétaire.The reference &nom points to the data but doesn't own it: when it disappears, nothing is freed. nom stays the sole owner.

Deux sortes d'empruntTwo kinds of borrow

&T — emprunt partagé : pour lire. On peut en avoir autant qu'on veut en même temps. &mut T — emprunt exclusif : pour muter. Il ne peut y en avoir qu'un, et aucun autre accès pendant ce temps. Toute la suite tient dans la tension entre ces deux-là.&T — shared borrow: to read. You can have as many as you like at once. &mut T — exclusive borrow: to mutate. There can be only one, and no other access while it lives. Everything that follows fits in the tension between these two.

Le réflexe du numéro.The issue's reflex.

« Cette donnée — je la lis, ou je la mute ? Et combien d'accès en même temps ? » La réponse choisit la référence : & pour lire à plusieurs, &mut pour muter seul. Se tromper de réponse, c'est le combat contre le borrow checker.'This data — am I reading it, or mutating it? And how many accesses at once?' The answer picks the reference: & to read with others, &mut to mutate alone. Getting the answer wrong is the fight against the borrow checker.

№ 02Le conflitThe conflict4 min

Lire et muter en même temps. Le bug que Python te laisse écrire.Read and mutate at once. The bug Python lets you write.

Itérer sur une collection tout en la modifiant : un classique. En Python ou en JS, le code tourne — et l'itérateur saute un élément, ou explose, selon l'humeur. C'est l'invalidation d'itérateur, un bug d'aliasing : deux accès à la même donnée, dont l'un mute. Rust regarde exactement ces deux accès et refuse de compiler.Iterating over a collection while modifying it: a classic. In Python or JS, the code runs — and the iterator skips an element, or blows up, depending on its mood. That's iterator invalidation, an aliasing bug: two accesses to the same data, one of them mutating. Rust looks at exactly those two accesses and refuses to compile.

Python — muter en itérant, sans une plaintePython — mutate while iterating, without a complaint
items = [1, 2, 2, 3]
for x in items:          # on ITÈRE (on lit)
    if x == 2:
        items.remove(x)  # ... et on MUTE pendant l'itération
# pas d'erreur — mais l'itérateur saute le second 2 : [1, 2, 3], faux

Aucune erreur levée. L'itérateur avance d'un cran pendant que la liste rétrécit sous lui : il saute l'élément suivant. Le résultat est silencieusement faux. Le bug ne vit pas dans remove ni dans for pris séparément — il naît de leur simultanéité.No error raised. The iterator steps forward while the list shrinks beneath it: it skips the next element. The result is silently wrong. The bug lives neither in remove nor in for taken alone — it's born of their simultaneity.

L'invalidation d'itérateur n'est pas attrapée par un test en Rust. Elle ne se compile pas.Iterator invalidation isn't caught by a test in Rust. It doesn't compile.
№ 03Aliasing XOR mutationAliasing XOR mutation9 min

Plusieurs lecteurs, ou un seul écrivain. Jamais les deux.Many readers, or one writer. Never both.

Voici la règle d'or, et elle tient en une ligne : à tout instant, une donnée peut avoir soit autant de références partagées qu'on veut, soit exactement une référence exclusive — jamais les deux à la fois. Ce n'est pas une contrainte arbitraire du langage. C'est, exactement, la condition qui rend une course de données impossible à écrire.Here's the golden rule, and it fits on one line: at any instant, a value may have either as many shared references as you like, or exactly one exclusive reference — never both at once. This is no arbitrary language constraint. It is, exactly, the condition that makes a data race impossible to write.

Les quatre combinaisons — et le verdict du compilateurThe four combinations — and the compiler's verdict
&T + &T
✓ compile✓ compilesAutant de lecteurs qu'on veut : personne ne mute, rien à craindre.As many readers as you like: nobody mutates, nothing to fear.
&mut T
✓ compile✓ compilesUn seul écrivain, et aucun autre accès : l'exclusivité est garantie.A single writer, and no other access: exclusivity is guaranteed.
&mut T + &T
✕ refusé✕ refusedUn lecteur verrait la donnée changer sous lui. Refusé.A reader would see the data change beneath it. Refused.
&mut T + &mut T
✕ refusé✕ refusedDeux écrivains : la course de données elle-même. Refusé.Two writers: the data race itself. Refused.

Lecture seule = partageable. Écriture = exclusive. Le XOR est dans le « jamais les deux ».Read-only = shareable. Write = exclusive. The XOR is in the 'never both'.

L'emprunt ne dure que jusqu'à son dernier usage (NLL)A borrow lasts only until its last use (NLL)

La règle effraie moins qu'il n'y paraît, parce qu'un emprunt ne vit pas jusqu'à la fin du bloc : il meurt à sa dernière utilisation. C'est le non-lexical lifetime. Une fois le lecteur fini de lire, l'écrivain peut entrer.The rule is less frightening than it looks, because a borrow doesn't live to the end of the block: it dies at its last use. That's the non-lexical lifetime. Once the reader is done reading, the writer may step in.

let mut s = String::from("hello");

let r = &s;            // emprunt partagé commence
println!("{r}");       // ... et finit ICI, à son dernier usage

s.push_str(" world");  // &mut s : autorisé, le & est déjà mort
println!("{s}");       // hello world

À vous — le compilateur répond pour de bonYour turn — the compiler answers for real

Trois programmes refusés, trois lignes à déplacer. Rien à supprimer, rien à cloner — le sous-ensemble n'a même pas de .clone(), ce n'est pas une limite mais l'énoncé. Ce qui répond n'est pas une correction enregistrée : c'est un borrow checker qui recalcule les régions à chaque frappe, et dont les verdicts sont confrontés au vrai rustc à chaque build du site. La colonne de droite dessine chaque emprunt du & jusqu'à son dernier usage : deux barres qui se chevauchent sur la même valeur, c'est l'erreur, avant même de la lire.Three rejected programs, three lines to move. Nothing to delete, nothing to clone — the subset doesn't even have .clone(), which isn't a limitation but the exercise. What answers isn't a recorded correction: it's a borrow checker recomputing regions on every keystroke, whose verdicts are checked against the real rustc on every build of this site. The right-hand column draws each borrow from its & to its last use: two bars overlapping on the same value is the error, before you've even read it.

`r` lit `s`, puis `s` est modifiée : les deux ne peuvent pas se chevaucher. Faites compiler sans rien supprimer — la lecture par `r` et la mutation de `s` doivent toutes deux rester.`r` reads `s`, then `s` is modified: the two cannot overlap. Make it compile without deleting anything — the read through `r` and the mutation of `s` must both remain.

&s
E0502 · ligne 3line 3
cannot borrow `s` as mutable because it is also borrowed as immutablecannot borrow `s` as mutable because it is also borrowed as immutable
Compiler ne suffit pasCompiling isn't enough
✓`r` emprunte `s` en partagé, et le relit`r` borrows `s` immutably, and actually reads it
✓`s` est toujours modifiée`s` is still modified

Supprimer la ligne qui gêne fait toujours compiler — et n'apprend rien. C'est ce que ces deux lignes vérifient.Deleting the offending line always compiles — and teaches nothing. That's what these two lines check.

Pourquoi cette règle, précisémentWhy this rule, precisely

Une course de données, c'est par définition : deux accès à la même mémoire, au moins un en écriture, sans synchronisation. La règle « aliasing XOR mutation » interdit exactement cette configuration — soit personne ne mute (que des &), soit un seul accède (un &mut, exclusif). Le data race n'est pas détecté : il devient inexprimable. Et au Vol 7, on découvrira que la concurrence « sans peur » n'est rien d'autre que cette même règle, appliquée aux threads.A data race is, by definition: two accesses to the same memory, at least one a write, without synchronization. The 'aliasing XOR mutation' rule forbids exactly that configuration — either nobody mutates (only &), or only one accesses (a single, exclusive &mut). The data race isn't detected: it becomes inexpressible. And in Vol 7 we'll find that 'fearless' concurrency is nothing but this same rule, applied to threads.

№ 04Le piègeThe trap4 min

Dégainer Rc<RefCell> pour faire taire le borrow checker.Reaching for Rc<RefCell> to silence the borrow checker.

Le borrow checker refuse ton code. Deux échappatoires tentantes : cloner partout (on retombe dans le piège du numéro précédent), ou emballer la donnée dans Rc<RefCell> pour récupérer le partage mutable d'un langage à GC. Les deux compilent. Les deux réintroduisent à l'exécution exactement ce que la règle avait éliminé à la compilation — le clone son coût, le RefCell son panic.The borrow checker refuses your code. Two tempting escape hatches: clone everywhere (back into the previous issue's trap), or wrap the data in Rc<RefCell> to win back a GC language's shared mutation. Both compile. Both reintroduce at runtime exactly what the rule eliminated at compile time — the clone its cost, the RefCell its panic.

Le contre-exemple — déplacer le borrow check à l'exécutionThe counter-example — moving the borrow check to runtime
// « Le borrow checker crie, je dégaine Rc<RefCell> »
let shared = Rc::new(RefCell::new(items));
let a = Rc::clone(&shared);
let b = Rc::clone(&shared);
a.borrow_mut().push(1);
b.borrow_mut().push(2);   // compile... et panique à l'exécution si les
                          // deux borrow_mut se chevauchent : « RefCell already borrowed »

RefCell ne supprime pas la règle « aliasing XOR mutation » : il la déplace du compilateur vers l'exécution. Deux borrow_mut() qui se chevauchent ne sont plus une erreur de compilation mais un panic à l'exécution (« RefCell already borrowed ») — le bug que le Vol 1 t'avait épargné, ressuscité, et cette fois invisible jusqu'au crash. On y reviendra au Vol 6 ; ici, c'est le réflexe qu'on dénonce, pas l'outil.RefCell doesn't remove the 'aliasing XOR mutation' rule: it moves it from the compiler to runtime. Two overlapping borrow_mut() calls are no longer a compile error but a runtime panic ('RefCell already borrowed') — the bug Vol 1 spared you, resurrected, and this time invisible until the crash. We'll return to it in Vol 6; here, it's the reflex we indict, not the tool.

Le bon réflexe — raccourcir l'empruntThe right reflex — shorten the borrow
// On garde l'emprunt court : le conflit disparaît de lui-même
let len = {
    let view = &items;        // emprunt partagé
    view.len()
};                            // l'emprunt meurt ici
items.push(len);              // &mut libre : plus aucun chevauchement

Neuf fois sur dix, le conflit vient d'un emprunt qui traîne au-delà de son utilité. Un bloc { }, une variable intermédiaire, et le partagé meurt avant que l'exclusif n'arrive. Pas de clone, pas de RefCell : juste un emprunt qui ne dure que ce qu'il doit.Nine times out of ten, the conflict comes from a borrow that lingers past its usefulness. A { } block, an intermediate variable, and the shared borrow dies before the exclusive one arrives. No clone, no RefCell: just a borrow that lasts only as long as it must.

L'autre erreur : &mut par défautThe other mistake: &mut by default

Le symétrique du clone réflexe : prendre &mut « au cas où », là où & suffisait. Un &mut verrouille la donnée en exclusif et fait échouer tous les emprunts partagés autour — pour une mutation qui n'a parfois jamais lieu. Demande le minimum : si tu ne mutes pas, n'exige pas l'exclusivité.The mirror of the reflex clone: taking &mut 'just in case', where & would do. A &mut locks the data exclusively and breaks every shared borrow around it — for a mutation that sometimes never happens. Ask for the minimum: if you don't mutate, don't demand exclusivity.

Le test mentalThe mental test

Devant une erreur d'emprunt : « Est-ce que cet emprunt a besoin de vivre aussi longtemps ? Et ai-je vraiment besoin de muter ? » Avant Rc<RefCell>, avant clone : raccourcis, ou rétrograde &mut en &. L'échappatoire à l'exécution est le dernier recours, jamais le premier.Facing a borrow error: 'Does this borrow need to live that long? And do I really need to mutate?' Before Rc<RefCell>, before clone: shorten, or downgrade &mut to &. The runtime escape hatch is the last resort, never the first.

№ 05Bilan & éditoWrap-up & editorial2 min

Une règle, et la moitié de Rust en découle.One rule, and half of Rust follows.

Le move disait : une valeur, un propriétaire. L'emprunt ajoute la nuance qui rend le modèle vivable — on peut prêter sans céder, à condition de respecter une seule règle. Tiens-la, et le data race, l'invalidation d'itérateur, le use-after-free disparaissent du domaine du possible.The move said: one value, one owner. Borrowing adds the nuance that makes the model livable — you can lend without giving up, as long as you honor a single rule. Hold it, and the data race, iterator invalidation, and use-after-free leave the realm of the possible.

La référenceThe referenceCe qu'elle autoriseWhat it allowsLa règleThe rule
&TLire la donnée sans la posséder. Plusieurs en même temps, sans limite.Read the data without owning it. Several at once, no limit.Tant qu'un seul & est vivant, aucun &mut ne peut exister.While any & is alive, no &mut may exist.
&mut TMuter la donnée sans la posséder. Un seul, exclusif.Mutate the data without owning it. One only, exclusive.Pendant qu'un &mut vit, aucun autre accès — ni & ni &mut.While a &mut lives, no other access — neither & nor &mut.
DuréeLifespanL'emprunt meurt à son dernier usage (NLL), pas à la fin du bloc.A borrow dies at its last use (NLL), not at the block's end.Raccourcir l'emprunt désamorce la plupart des conflits.Shortening a borrow defuses most conflicts.
L'issueThe exitConflit persistant ? Restructure la possession — n'emballe pas dans Rc<RefCell> par réflexe.Persistent conflict? Restructure ownership — don't wrap in Rc<RefCell> by reflex.L'échappatoire à l'exécution déplace le bug, elle ne le supprime pas.The runtime escape hatch moves the bug, it doesn't remove it.
« Aliasing XOR mutation » n'est pas une règle de Rust. C'est la définition même d'une course de données, retournée en interdiction."Aliasing XOR mutation" isn't a Rust rule. It's the very definition of a data race, flipped into a prohibition.
Mot de l'éditeurFrom the editor

Le borrow checker ne vérifie pas ton code.The borrow checker doesn't check your code.

On l'appelle « checker », comme s'il passait derrière toi relever les fautes. C'est trompeur. Il ne cherche pas des bugs dans ton code : il refuse de donner un sens aux programmes où la règle est violée. La nuance est tout. Un linter te signale un risque que tu peux ignorer ; le borrow checker énonce un théorème — tant que ce code existe, telle catégorie de bug est mathématiquement absente. Cesser de le combattre, c'est arrêter d'essayer d'écrire les programmes qu'il déclare dénués de sens.We call it a 'checker', as if it walked behind you flagging mistakes. That's misleading. It isn't hunting for bugs in your code: it refuses to give meaning to programs where the rule is violated. The nuance is everything. A linter flags a risk you can ignore; the borrow checker states a theorem — as long as this code exists, that class of bug is mathematically absent. To stop fighting it is to stop trying to write the programs it declares meaningless.

Prochain numéro : les slices. Emprunter une fenêtre d'une donnée — &str, &[T] — sans la copier ni la posséder. Le réflexe qui, à lui seul, désamorce la plupart des combats qu'on vient de voir.Next issue: slices. Borrowing a window onto data — &str, &[T] — without copying or owning it. The reflex that, on its own, defuses most of the fights we just saw.