Vol. 9 · № 03 · Un atelier RustA Rust workshop

unsafe & FFI, ou le dernier recours encapsulé unsafe & FFI, or the encapsulated last resort

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.

AudienceAudience
Dev ayant parcouru la série — possession, types, concurrence (Vol 1–8) Dev who's gone through the series — ownership, types, concurrency (Vol 1–8)
Format
Self-paced
ChapitresChapters
5
Date
Juin 2026 Jun 2026
≈ 18 min ●●●● CoûtunsafeFFI
SommaireContents ·
№ 01CadrageFraming3 min

unsafe ne désactive aucune règle : il transfère leur preuve du compilateur vers toi.unsafe disables no rule: it transfers their proof from the compiler to you.

Voici le dernier numéro de la série, et son sujet est l'unique porte de sortie du système de preuve. Pendant huit volumes, on a laissé le compilateur prouver la sûreté : la possession, les durées de vie, l'absence de course. unsafe est la porte par laquelle on en sort. Mais attention au contre-sens le plus répandu : unsafe ne « désactive » pas les règles de Rust. Il fait une seule chose — autoriser cinq opérations que le compilateur ne sait pas vérifier (déréférencer un pointeur brut, appeler une fonction unsafe, toucher un static mutable, implémenter un trait unsafe, lire un champ d'union) — à une condition : que ce soit toi, désormais, qui garantisses les invariants que le compilateur vérifiait à ta place. Le contrat qu'il tenait, tu le signes. Le tenir, et tout va bien ; le rompre, et c'est l'undefined behavior — le bug que rien ne rattrape.Here's the series' final issue, and its subject is the single exit from the proof system. For eight volumes, we let the compiler prove safety: ownership, lifetimes, the absence of races. unsafe is the door you leave through. But beware the most widespread misreading: unsafe does not 'disable' Rust's rules. It does one thing — allow five operations the compiler can't verify (dereference a raw pointer, call an unsafe function, touch a mutable static, implement an unsafe trait, read a union field) — on one condition: that it's now you who guarantees the invariants the compiler checked in your place. The contract it held, you sign. Uphold it, and all is well; break it, and it's undefined behavior — the bug nothing catches.

La preuve change de main — du compilateur vers toiThe proof changes hands — from the compiler to you
safe Rustsafe Rust
le COMPILATEUR prouvethe COMPILER proves
possession, lifetimes, aliasing — vérifiésownership, lifetimes, aliasing — checked
unsafe { … }la preuve ▸the proof ▸change de garantchanges guarantor
bloc unsafeunsafe block
TOI qui prouvesYOU prove
mêmes invariants — garantis à la mainsame invariants — guaranteed by hand
Une promesse que le compilateur ne vérifie plusA promise the compiler no longer checks
let x = 42;
let ptr = &x as *const i32;     // un pointeur brut : le CRÉER est sûr…

let valeur = unsafe { *ptr };   // …le DÉRÉFÉRENCER ne l'est pas. unsafe = « je PROMETS que
                                //   ce pointeur est valide, aligné, et pointe une donnée vivante ».
// le compilateur ne vérifie plus cette promesse : c'est TOI qui réponds de sa véracité.
// la tenir → tout va bien. la rompre → undefined behavior, le pire des bugs.

Créer un pointeur brut est sûr — ce n'est qu'un nombre. Le déréférencer ne l'est pas : rien ne garantit qu'il pointe encore vers une donnée vivante, valide, bien alignée. En safe Rust, c'est exactement ce qu'une référence & prouve, et que le compilateur impose. Dans le bloc unsafe, il te laisse déréférencer — mais la garantie, c'est toi qui la portes. unsafe n'est donc pas « moins de règles » ; c'est les mêmes règles, avec un autre garant. Et ce garant, faillible, c'est l'humain.Creating a raw pointer is safe — it's just a number. Dereferencing it isn't: nothing guarantees it still points to live, valid, well-aligned data. In safe Rust, that's exactly what a & reference proves, and what the compiler enforces. In the unsafe block, it lets you dereference — but the guarantee, you carry. unsafe isn't 'fewer rules'; it's the same rules, with another guarantor. And that guarantor, fallible, is the human.

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

« Cet unsafe est-il encapsulé derrière une API sûre dont je peux prouver les invariants — ou éparpillé parce que je n'ai pas compris l'erreur ? » C'est la question qui sépare le bon usage du mauvais. Le bon unsafe est rare, localisé, justifié par un commentaire // SAFETY:, et enfermé derrière une fonction sûre. Le mauvais est diffus, posé pour faire taire le compilateur, et fait peser sur tout le programme un risque d'UB qu'aucun test ne révèle.'Is this unsafe encapsulated behind a safe API whose invariants I can prove — or scattered because I didn't understand the error?' That's the question separating good use from bad. Good unsafe is rare, localized, justified by a // SAFETY: comment, and locked behind a safe function. Bad unsafe is diffuse, dropped to silence the compiler, and makes the whole program carry a risk of UB no test reveals.

UB : pas un crash, une trahison.UB: not a crash, a betrayal.

L'undefined behavior n'est pas « le programme plante ». C'est pire et plus sournois. Le compilateur optimise en supposant que l'UB n'arrive jamais — c'est une hypothèse sur laquelle il bâtit tout son raisonnement. Si tu violes cette hypothèse, il a déjà compilé du code qui en dépend : le résultat peut être un crash, mais aussi une corruption silencieuse, une faille de sécurité, un comportement qui change selon le niveau d'optimisation ou la version du compilateur. L'UB n'est pas un bug local : c'est l'effondrement de toutes les garanties, partout, de façon imprévisible. D'où la gravité du contrat qu'unsafe te fait signer.Undefined behavior isn't 'the program crashes'. It's worse and more insidious. The compiler optimizes assuming UB never happens — it's an assumption on which it builds all its reasoning. If you violate that assumption, it has already compiled code depending on it: the result can be a crash, but also silent corruption, a security hole, a behavior that changes with the optimization level or the compiler version. UB isn't a local bug: it's the collapse of all guarantees, everywhere, unpredictably. Hence the gravity of the contract unsafe makes you sign.

№ 02La surpriseThe surprise4 min

Dans un bloc unsafe, le borrow checker est toujours allumé.Inside an unsafe block, the borrow checker is still on.

La surprise du numéro corrige l'image la plus répandue et la plus fausse : « unsafe, c'est le mode C, tout est permis ». Non. À l'intérieur d'un bloc unsafe, le borrow checker fonctionne exactement comme dehors : tu ne peux toujours pas avoir deux &mut sur la même donnée, tu dois toujours respecter les durées de vie, les moves restent vérifiés. unsafe n'ouvre que cinq portes précises — déréférencer un pointeur brut, appeler une fonction unsafe, toucher un static mutable, implémenter un trait unsafe, lire un champ d'union — et rien de plus. C'est une permission chirurgicale, pas un interrupteur général. Cette étroitesse est une bonne nouvelle : la zone où tu dois faire la preuve toi-même est minuscule, circonscrite à ces cinq opérations. Tout le reste de ton code, même dans un bloc unsafe, reste prouvé par le compilateur.The issue's surprise corrects the most widespread and most false image: 'unsafe is C mode, anything goes'. No. Inside an unsafe block, the borrow checker works exactly as outside: you still can't have two &mut on the same data, you must still respect lifetimes, moves are still checked. unsafe opens only five precise doors — dereference a raw pointer, call an unsafe function, touch a mutable static, implement an unsafe trait, read a union field — and nothing more. It's a surgical permission, not a master switch. That narrowness is good news: the zone where you must do the proof yourself is tiny, confined to those five operations. All the rest of your code, even in an unsafe block, stays proven by the compiler.

Deux &mut restent refusés — même dans unsafeTwo &mut stay rejected — even in unsafe
let mut data = vec![1, 2, 3];

unsafe {
    let r1 = &mut data;
    let r2 = &mut data;       // ✕ error[E0499]: cannot borrow `data` as mutable more than once
    r1.push(4);               //   le bloc unsafe N'A PAS désactivé le borrow checker.
}
// la règle « aliasing XOR mutation » du Vol 1 tient TOUJOURS ici. unsafe ne l'a pas levée :
// il n'a jamais été question de désactiver les règles, seulement d'autoriser cinq opérations.

Le même E0499 que partout ailleurs. Le bloc unsafe n'a strictement rien changé à l'analyse des emprunts : la règle « aliasing XOR mutation » du Vol 1 s'applique à l'identique. C'est la preuve par le code que unsafe n'est pas un bypass. Si tu veux deux &mut sur la même donnée, unsafe ne t'y aide pas — il faudrait passer par des pointeurs bruts (porte n°1) et prouver toi-même qu'ils ne s'aliasent pas. Le compilateur ne te lâche pas la main ; il te tend juste un outil de plus, et la responsabilité qui va avec.The same E0499 as everywhere else. The unsafe block changed strictly nothing in the borrow analysis: Vol 1's 'aliasing XOR mutation' rule applies identically. It's proof by code that unsafe isn't a bypass. If you want two &mut on the same data, unsafe doesn't help — you'd go through raw pointers (door 1) and prove yourself they don't alias. The compiler doesn't let go of your hand; it just hands you one more tool, and the responsibility that comes with it.

unsafe n'est pas « le mode C » : c'est cinq permissions précises, et le borrow checker veille sur tout le reste, dedans comme dehors.unsafe isn't 'C mode': it's five precise permissions, and the borrow checker watches over everything else, inside as out.
№ 03La mécaniqueThe mechanics5 min

Le bon unsafe : un cœur minuscule, prouvé une fois, derrière une API sûre.Good unsafe: a tiny core, proven once, behind a safe API.

Si unsafe est si dangereux, pourquoi la std en est-elle truffée ? Parce que c'est exactement ainsi qu'on bâtit une abstraction sûre par-dessus une opération que le compilateur ne sait pas vérifier. Le patron est invariable : un cœur unsafe minuscule, qui maintient un invariant précis, enfermé derrière une API dont la signature est, elle, parfaitement sûre. Prends couper, qui rend deux &mut sur deux moitiés disjointes d'un slice : le borrow checker ne peut pas prouver que les moitiés ne se chevauchent pas, alors on descend aux pointeurs bruts — mais on prouve nous-mêmes la disjonction, une seule fois, avec un commentaire // SAFETY: qui l'explicite. L'appelant, lui, n'écrit jamais unsafe : il reçoit deux références sûres. Tout Vec, tout itérateur du №1, repose là-dessus. C'est aussi le seul moyen de parler au monde extérieur — le C, via extern «C» — qui est unsafe par nature, et qu'on enveloppe de la même façon.If unsafe is so dangerous, why is the std riddled with it? Because that's exactly how you build a safe abstraction over an operation the compiler can't verify. The pattern is invariable: a tiny unsafe core, maintaining a precise invariant, locked behind an API whose signature is perfectly safe. Take couper, returning two &mut over two disjoint halves of a slice: the borrow checker can't prove the halves don't overlap, so you drop to raw pointers — but you prove the disjointness yourself, just once, with a // SAFETY: comment spelling it out. The caller never writes unsafe: they receive two safe references. Every Vec, every iterator from №1, rests on this. It's also the only way to talk to the outside world — C, via extern «C» — which is unsafe by nature, and which you wrap the same way.

Le cœur unsafe, enfermé sous une frontière sûreThe unsafe core, locked under a safe boundary
monde sûr — les appelants n'écrivent jamais unsafesafe world — callers never write unsafe
API SÛRE : fn couper(&mut [i32], usize) → (&mut, &mut)SAFE API: fn couper(&mut [i32], usize) → (&mut, &mut)
cœur unsafeunsafe core
from_raw_parts_mut(…)
// SAFETY: les moitiés sont disjointes// SAFETY: the halves are disjoint
le danger est confiné au plus petit cœur possible — prouvé une seule foisthe danger is confined to the smallest possible core — proven once
Une API sûre par-dessus un invariant prouvé à la mainA safe API over a hand-proven invariant
use std::slice;

// le compilateur ne PEUT pas prouver que deux &mut visent des moitiés DISJOINTES d'un slice.
// on le prouve nous-mêmes, une fois, dans un cœur unsafe — derrière une API sûre :
fn couper(v: &mut [i32], i: usize) -> (&mut [i32], &mut [i32]) {
    let len = v.len();
    assert!(i <= len);                // l'assert FAIT PARTIE de la preuve : sans lui,
    let p = v.as_mut_ptr();           // un i > len produirait un slice hors bornes → UB
    // SAFETY: l'assert garantit i <= len, donc [0, i) et [i, len) ne se chevauchent pas
    // → deux &mut disjoints.
    unsafe {
        (slice::from_raw_parts_mut(p, i),
         slice::from_raw_parts_mut(p.add(i), len - i))
    }
}
// l'appelant n'écrit JAMAIS unsafe : il reçoit deux &mut sûrs. l'invariant est prouvé ICI, une fois.

Le commentaire // SAFETY: n'est pas décoratif : c'est le cœur du contrat. Il énonce, en clair, l'invariant qui rend le bloc unsafe correct — ici, que les deux moitiés ne se chevauchent pas. C'est lui qu'un relecteur vérifie, lui qui documente la preuve que tu as faite à la place du compilateur. Un bloc unsafe sans // SAFETY: est un contrat signé sans en lire les termes. La discipline professionnelle : aucun unsafe sans le commentaire qui justifie pourquoi il est sûr.The // SAFETY: comment isn't decorative: it's the heart of the contract. It states, plainly, the invariant making the unsafe block correct — here, that the two halves don't overlap. It's what a reviewer checks, what documents the proof you did in the compiler's place. An unsafe block with no // SAFETY: is a contract signed without reading its terms. The professional discipline: no unsafe without the comment justifying why it's safe.

FFI : envelopper un appel C dans une frontière sûreFFI: wrapping a C call in a safe boundary
// FFI : parler à du C est unsafe par nature — le compilateur ne voit pas l'autre côté
unsafe extern "C" {                  // édition 2024 : le bloc extern est lui-même unsafe
    fn abs(input: i32) -> i32;        // déclaration d'une fonction C
}

pub fn valeur_absolue(x: i32) -> i32 {   // ← API Rust SÛRE exposée au reste du programme
    unsafe { abs(x) }                    // l'appel brut est unsafe ; on l'enveloppe ici
}
// même schéma : un cœur unsafe minuscule, une frontière sûre autour. le reste du code ignore le C.
Pourquoi la FFI est unsafe par nature.Why FFI is unsafe by nature.

Quand tu appelles une fonction C via extern «C», le compilateur Rust ne voit que sa signature — pas son corps. Il ne peut donc rien garantir : la fonction peut déréférencer n'importe quoi, ne pas respecter les durées de vie, supposer des invariants qu'il ignore. Tout appel à du code étranger est donc unsafe, par construction. Le travail du binding, c'est de lire la doc du C, de comprendre ses préconditions, et d'exposer une API Rust sûre qui les garantit — convertir les pointeurs, vérifier les codes d'erreur, encadrer les durées de vie. C'est exactement le même schéma que couper : un cœur unsafe, une frontière sûre, un invariant prouvé une fois.When you call a C function via extern «C», the Rust compiler sees only its signature — not its body. So it can guarantee nothing: the function may dereference anything, ignore lifetimes, assume invariants it knows nothing of. Any call to foreign code is therefore unsafe, by construction. The binding's job is to read the C docs, understand its preconditions, and expose a safe Rust API that guarantees them — convert pointers, check error codes, frame lifetimes. It's exactly the same pattern as couper: an unsafe core, a safe boundary, an invariant proven once.

Le bon unsafe ne fuit pas : un cœur prouvé une fois, une frontière sûre autour — et un // SAFETY: qui dit pourquoi.Good unsafe doesn't leak: a core proven once, a safe boundary around it — and a // SAFETY: saying why.
№ 04Le piègeThe trap4 min

unsafe pour faire taire le borrow checker — et réintroduire le bug du Vol 1.unsafe to silence the borrow checker — and bring back Vol 1's bug.

Voici le contre-exemple qui clôt la série, et c'est le pire usage d'unsafe : s'en servir pour contourner une erreur de borrow checker qu'on n'a pas comprise. Le scénario est universel. On veut deux &mut sur des éléments d'un même Vec, le compilateur refuse, et plutôt que de se demander pourquoi, on dégaine les pointeurs bruts : un unsafe, deux déréférencements, et ça compile. Sauf qu'on n'a rien résolu — on a déplacé le problème de la compilation vers l'exécution, et repris à sa charge une preuve qu'on est précisément incapable de faire, puisqu'on n'a pas compris l'erreur. Si les deux indices peuvent être égaux, c'est de l'aliasing mutable : exactement l'aliasing mutable que tout le Vol 1 rendait impossible, réintroduite à la main, avec en prime le risque d'UB. Le borrow checker ne dressait pas un mur arbitraire : il posait une question de design. unsafe ne répond pas à la question — il la fait taire.Here's the counter-example that closes the series, and it's the worst use of unsafe: using it to dodge a borrow-checker error you didn't understand. The scenario is universal. You want two &mut on elements of the same Vec, the compiler refuses, and rather than asking why, you draw raw pointers: an unsafe, two dereferences, and it compiles. Except you've solved nothing — you've moved the problem from compile time to runtime, and taken back a proof you're precisely unable to make, since you didn't understand the error. If the two indices can be equal, it's mutable aliasing: exactly the mutable aliasing all of Vol 1 made impossible, reintroduced by hand, with the bonus risk of UB. The borrow checker wasn't raising an arbitrary wall: it was asking a design question. unsafe doesn't answer the question — it silences it.

Deux issues à la même erreur — l'une rouvre l'UB, l'autre la fermeTwo exits from the same error — one reopens UB, one closes it
unsafe pour contournerunsafe to dodge
erreur de borrow checkerborrow-checker error
↓
pointeurs bruts + unsaferaw pointers + unsafe
↓
ça compile…it compiles…
↓
aliasing mutable → UBmutable aliasing → UB
le bug du Vol 1, réintroduitVol 1's bug, reintroduced
refactor de possessionownership refactor
erreur de borrow checkerborrow-checker error
↓
que demande-t-elle ?what is it asking?
↓
accès séquentiels / arènesequential access / arena
↓
prouvé à la compilationproven at compile time
zéro unsafe, zéro risquezero unsafe, zero risk
Le contre-exemple — unsafe pour forcer deux &mutThe counter-example — unsafe to force two &mut
// ✕ le combat : pousser dans noeuds[i] ET muter noeuds[j]. le borrow checker refuse
//   deux &mut sur le même Vec. au lieu de repenser la possession, on FORCE avec des pointeurs :
let p = noeuds.as_mut_ptr();
unsafe {
    let a = &mut *p.add(i);        // deux &mut sur le même Vec, via pointeurs bruts —
    let b = &mut *p.add(j);        // si i == j, c'est de l'ALIASING MUTABLE → undefined behavior
    a.fils.push(j);
    b.valeur += 1;
}
// on vient de réintroduire, à la main, exactement l'aliasing mutable que tout le Vol 1 rendait IMPOSSIBLE.
Le bon réflexe — écouter l'erreur, restructurer la possessionThe right reflex — listen to the error, restructure ownership
// ✓ le refactor de possession : ne jamais tenir deux &mut, agir en deux temps
noeuds[i].fils.push(j);            // un seul &mut à la fois, vérifié à la compilation
noeuds[j].valeur += 1;             // emprunt séparé, séquentiel — aucun chevauchement

// s'il faut vraiment les deux &mut en même temps : noeuds.get_disjoint_mut([i, j])
// rend une Err si i == j — la preuve de disjonction, faite par la std.
// si la structure l'exige vraiment, l'arène du Vol 6 №3 : un Vec + des indices (usize),
// le borrow checker reprend tout son pouvoir. aucun unsafe, aucun risque d'UB.

La version sûre est aussi la plus simple : deux accès séquentiels, chacun avec un seul &mut vérifié. Pas de pointeurs, pas de // SAFETY: à honorer, pas de risque d'UB. Et si la structure réclame vraiment des accès croisés, le Vol 6 №3 avait la réponse : l'arène — un Vec propriétaire, des indices usize en guise de liens — qui rend au borrow checker tout son pouvoir. Une erreur de borrow checker est une question de design, pas un mur à percer. Le bon unsafe sert à faire ce que le langage ne peut pas exprimer — parler au C, bâtir une primitive — jamais à éviter de penser la possession.The safe version is also the simplest: two sequential accesses, each with a single checked &mut. No pointers, no // SAFETY: to honor, no risk of UB. And if the structure genuinely demands crossed accesses, Vol 6 №3 had the answer: the arena — an owning Vec, usize indices as links — which gives the borrow checker back its full power. A borrow-checker error is a design question, not a wall to drill through. Good unsafe serves to do what the language cannot express — talk to C, build a primitive — never to avoid thinking about ownership.

Le test avant tout unsafeThe test before any unsafe

Avant d'écrire unsafe, une seule question : « le langage peut-il exprimer ce que je veux, en safe Rust ? » Si oui — et c'est presque toujours oui pour un combat de borrow checker — alors unsafe est la mauvaise réponse, et la bonne est un refactor de possession. unsafe ne se justifie que lorsque la réponse est non : déréférencer un pointeur venu de l'extérieur, parler à du C, bâtir une primitive (un Vec, un allocateur) dont la sûreté repose sur un invariant qu'aucun type ne capture. Rare, encapsulé, prouvé. Jamais réflexe.Before writing unsafe, one question: 'can the language express what I want, in safe Rust?' If yes — and it almost always is, for a borrow-checker fight — then unsafe is the wrong answer, and the right one is an ownership refactor. unsafe is justified only when the answer is no: dereferencing a pointer from outside, talking to C, building a primitive (a Vec, an allocator) whose safety rests on an invariant no type captures. Rare, encapsulated, proven. Never reflexive.

Le coût caché de l'unsafe diffusThe hidden cost of scattered unsafe

Un unsafe encapsulé derrière une API sûre coûte une preuve, faite une fois, vérifiable. Un unsafe éparpillé pour faire taire des erreurs coûte bien plus : chaque bloc devient une hypothèse non vérifiée qui se propage. Comme l'UB peut corrompre n'importe quoi, n'importe où, un seul bloc fautif rend tout le programme suspect — on ne peut plus raisonner localement, la garantie de sûreté de Rust s'effondre pour l'ensemble. C'est pourquoi la règle est si stricte : minimiser la surface d'unsafe n'est pas du purisme, c'est préserver la capacité à raisonner sur le code.An unsafe encapsulated behind a safe API costs a proof, made once, verifiable. An unsafe scattered to silence errors costs far more: each block becomes an unverified assumption that propagates. Since UB can corrupt anything, anywhere, a single faulty block makes the whole program suspect — you can no longer reason locally, Rust's safety guarantee collapses for the whole. That's why the rule is so strict: minimizing the unsafe surface isn't purism, it's preserving the ability to reason about the code.

Une erreur de borrow checker est une question de design, pas un mur : unsafe pour la contourner réintroduit le bug que la série entière effaçait.A borrow-checker error is a design question, not a wall: unsafe to dodge it reintroduces the bug the whole series was erasing.
№ 05Bilan & éditoWrap-up & editorial2 min

La boucle se referme où elle a commencé : au Vol 1, la possession.The loop closes where it began: at Vol 1, ownership.

Ce dernier numéro révèle l'unique sortie du système de preuve. unsafe ne désactive aucune règle : il transfère leur preuve du compilateur vers toi, pour cinq opérations précises, le borrow checker restant allumé pour tout le reste. Le bon usage est rare et encapsulé — un cœur minuscule, prouvé une fois, justifié par un // SAFETY:, enfermé derrière une API sûre ; c'est ainsi qu'est bâtie toute la std, et qu'on parle au C. Le mauvais usage est diffus : poser unsafe pour faire taire un borrow checker qu'on n'a pas compris, et réintroduire à la main la course que le Vol 1 rendait impossible. La règle ferme la série comme elle l'a ouverte : une erreur du compilateur n'est pas un mur, c'est une question de design — et unsafe ne doit jamais servir à éviter d'y répondre.This final issue reveals the single exit from the proof system. unsafe disables no rule: it transfers their proof from the compiler to you, for five precise operations, the borrow checker staying on for all the rest. Good use is rare and encapsulated — a tiny core, proven once, justified by a // SAFETY:, locked behind a safe API; that's how the whole std is built, and how you talk to C. Bad use is diffuse: dropping unsafe to silence a borrow checker you didn't understand, and reintroducing by hand the race Vol 1 made impossible. The rule closes the series as it opened it: a compiler error isn't a wall, it's a design question — and unsafe must never serve to avoid answering it.

À propos d'unsafeAbout unsafeCe qu'il faut en retenirWhat to remember
ce qu'il faitwhat it doesTransfère la preuve de 5 opérations du compilateur vers toi.Transfers the proof of 5 operations from the compiler to you.
ce qu'il NE fait paswhat it does NOTDésactiver le borrow checker : il reste allumé pour tout le reste.Disable the borrow checker: it stays on for all the rest.
// SAFETY:Le contrat écrit : l'invariant qui rend le bloc correct. Obligatoire.The written contract: the invariant making the block correct. Mandatory.
le bon usagegood useRare, encapsulé derrière une API sûre. FFI, primitives, perfs prouvées.Rare, encapsulated behind a safe API. FFI, primitives, proven perf.
le piègethe trapContourner un borrow checker incompris → UB, le bug du Vol 1 de retour.Dodging a misunderstood borrow checker → UB, Vol 1's bug returns.
unsafe est le moment où l'on reprend, à la main, la preuve que la possession nous offrait — à n'ouvrir que lorsque le langage ne peut pas l'exprimer.unsafe is the moment you take back, by hand, the proof ownership gave you — to open only when the language can't express it.
Mot de l'éditeur — fin de la sérieFrom the editor — end of the series

Neuf volumes, une seule idée : le compilateur n'est pas un gardien, c'est une preuve.Nine volumes, one idea: the compiler isn't a guardian, it's a proof.

Regarde le chemin, d'un bout à l'autre. Le Vol 1 a posé la possession et sa règle unique — aliasing XOR mutation. Le Vol 2 l'a étendue dans le temps avec les lifetimes ; le Vol 3 l'a portée dans les types, pour rendre l'illégal irreprésentable ; le Vol 4 a fait de l'erreur une valeur ; le Vol 5 a partagé le comportement sans héritage ; le Vol 6 est sorti de l'arbre de possession, toujours à un prix ; le Vol 7 a appliqué la même règle aux threads, et le Vol 8 à l'attente sans blocage ; le Vol 9 a enfin nommé le coût, et la porte de sortie. Chaque volume semblait ajouter une fonctionnalité. Aucun ne le faisait. Tous déclinaient la même idée : en Rust, on ne débugge pas à l'exécution ce qu'on peut rendre impossible à la compilation.Look at the path, end to end. Vol 1 set ownership and its single rule — aliasing XOR mutation. Vol 2 extended it through time with lifetimes; Vol 3 carried it into types, to make the illegal unrepresentable; Vol 4 made error a value; Vol 5 shared behavior without inheritance; Vol 6 left the ownership tree, always at a price; Vol 7 applied the same rule to threads, and Vol 8 to waiting without blocking; Vol 9 finally named the cost, and the exit. Each volume seemed to add a feature. None did. All declined the same idea: in Rust, you don't debug at runtime what you can make impossible at compile time.

C'est pourquoi le combat contre le compilateur est un malentendu. Ses refus ne sont pas des caprices à contourner par un clone, un Rc<RefCell>, un unsafe. Chacun est un théorème : il te dit qu'une propriété que tu croyais tenir ne tient pas — qu'une référence survivrait à sa donnée, qu'un état serait partagé et muté, qu'un invariant n'est pas garanti. Mémoriser les incantations qui « font passer » le borrow checker, c'est rester sourd à ce qu'il prouve. L'écouter, c'est apprendre à voir les bugs avant de les écrire.That's why fighting the compiler is a misunderstanding. Its refusals aren't whims to dodge with a clone, an Rc<RefCell>, an unsafe. Each is a theorem: it tells you a property you thought held doesn't — that a reference would outlive its data, that a state would be shared and mutated, that an invariant isn't guaranteed. Memorizing the incantations that 'make the borrow checker pass' is staying deaf to what it proves. Listening to it is learning to see bugs before you write them.

C'était la promesse de la série, et c'est là qu'elle se tient : à la fin, tu ne te bats plus contre le compilateur. Tu l'entends te dire quel théorème tu n'as pas encore su énoncer. Merci d'avoir lu The Rust Loop jusqu'ici — vingt-sept numéros, neuf tensions, un seul réflexe. La suite, désormais, c'est ton propre code.That was the series' promise, and here it holds: in the end, you no longer fight the compiler. You hear it tell you which theorem you haven't yet managed to state. Thank you for reading The Rust Loop this far — twenty-seven issues, nine tensions, one reflex. What comes next is your own code.