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

Send, Sync & Arc<Mutex<T>> Send, Sync & Arc<Mutex<T>>

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.

AudienceAudience
Dev maîtrisant possession, Rc/Arc et les threads (Vol 1, Vol 6, Vol 7 №1) Dev fluent in ownership, Rc/Arc and threads (Vol 1, Vol 6, Vol 7 №1)
Format
Self-paced
ChapitresChapters
5
Date
Juin 2026 Jun 2026
≈ 19 min ●●●● ConcurrenceSendMutex
SommaireContents ·
№ 01CadrageFraming3 min

Deux traits qu'on n'écrit jamais décident ce qui a le droit de traverser un thread.Two traits you never write decide what is allowed to cross a thread.

Le numéro précédent a laissé une porte fermée. move donne une donnée à un seul thread ; thread::scope la prête à plusieurs, mais en lecture seule — parce que « aliasing XOR mutation » interdit que deux threads la tiennent pendant que l'un écrit. Reste le cas le plus courant en pratique : plusieurs threads qui veulent muter la même donnée. Un compteur, un cache, un état partagé. Avant d'y arriver, il faut nommer la mécanique invisible qui gouvernait déjà le refus du №1 : Send et Sync. Ce sont deux traits marqueurs, auto-dérivés par le compilateur, qui répondent à deux questions précises — cette donnée peut-elle partir dans un autre thread (Send) ? une référence vers elle peut-elle être partagée entre threads (Sync) ? Toute la sûreté de la concurrence en Rust repose sur ces deux réponses, calculées à la compilation, sans qu'on écrive une ligne.The previous issue left one door shut. move gives data to a single thread; thread::scope lends it to several, but read-only — because 'aliasing XOR mutation' forbids two threads holding it while one writes. That leaves the most common case in practice: several threads wanting to mutate the same data. A counter, a cache, a shared state. Before getting there, we must name the invisible machinery that already governed the №1 refusal: Send and Sync. These are two marker traits, auto-derived by the compiler, answering two precise questions — can this data leave for another thread (Send)? can a reference to it be shared between threads (Sync)? All of Rust's concurrency safety rests on these two answers, computed at compile time, without writing a line.

Send et Sync — qui peut traverser, qui peut être partagéSend and Sync — who can cross, who can be shared
typetype
Send
Sync
pourquoiwhy
i32, String, Vec<T>
✓
✓
le cas normalthe normal case
Rc<T>
✕
✕
compteur non atomiquenon-atomic counter
Arc<T>
✓
✓
compteur atomique (Vol 6)atomic counter (Vol 6)
RefCell<T>
✓
✕
borrow check non synchroniséunsynchronized borrow check
Mutex<T>
✓
✓
verrou → exclusivité sûrelock → safe exclusivity
L'autre moitié de la signature de spawnThe other half of spawn's signature
// la signature de thread::spawn impose DEUX bornes — pas une
pub fn spawn<F, T>(f: F) -> JoinHandle<T>
where
    F: FnOnce() -> T + Send + 'static,    // 'static (vu au №1) ET Send (ce numéro)
    T: Send + 'static;

// 'static  → « ne dépend d'aucun emprunt à durée limitée »   (le QUAND)
// Send     → « la possession peut PARTIR vers un autre thread » (le QUOI)
// le refus du №1 portait sur la durée de vie. voici l'autre moitié : ce qui a
// le DROIT de traverser. un Rc, par exemple, ne l'a pas — et le compilateur le sait.

Au №1, on a buté sur 'static : quand la donnée peut vivre. Mais spawn exige une seconde garantie — Send : quoi peut quitter le thread courant. Les deux sont des traits marqueurs : ils ne portent aucune méthode, ils étiquettent simplement un type comme sûr pour une opération. Et ils sont auto-dérivés : un type est Send (resp. Sync) si tous ses champs le sont. Tu n'écris presque jamais impl Send — le compilateur le déduit, et c'est ce calcul silencieux qui rend le data race inexprimable.In №1, we hit 'static: when the data may live. But spawn demands a second guarantee — Send: what may leave the current thread. Both are marker traits: they carry no method, they simply tag a type as safe for an operation. And they're auto-derived: a type is Send (resp. Sync) if all its fields are. You almost never write impl Send — the compiler deduces it, and that silent computation is what makes a data race inexpressible.

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

« Pour partager et muter entre threads, il faut deux outils, pas un : qui partage la possession, qui rétablit l'exclusivité ? » Arc répond au premier, Mutex au second — et ils vont ensemble parce que ni l'un ni l'autre ne suffit. Arc seul partage en lecture ; Mutex seul ne se partage pas. La concurrence sûre, c'est l'assemblage exact des deux.'To share and mutate across threads, you need two tools, not one: which shares ownership, which restores exclusivity?' Arc answers the first, Mutex the second — and they go together because neither alone suffices. Arc alone shares read-only; Mutex alone can't be shared. Safe concurrency is the exact assembly of both.

Send et Sync, en une phrase chacunSend and Sync, one sentence each

Send : la possession d'une valeur peut être transférée vers un autre thread (la valeur « part »). Sync : une référence partagée &T peut être utilisée depuis plusieurs threads à la fois — ce qui revient exactement à dire « &T est Send ». Presque tout est Send et Sync. Les exceptions sont instructives. Rc (compteur non atomique) n'est ni Send ni Sync. Cell et RefCell (mutabilité intérieure non synchronisée) sont Send — on peut les donner à un autre thread — mais pas Sync : on ne peut pas les partager. Les pointeurs bruts ne sont ni l'un ni l'autre. Chaque exception bloque exactement l'opération qui provoquerait une course.Send: ownership of a value can be transferred to another thread (the value 'leaves'). Sync: a shared reference &T can be used from several threads at once — which is exactly to say '&T is Send'. Almost everything is Send and Sync. The exceptions are instructive. Rc (non-atomic counter) is neither Send nor Sync. Cell and RefCell (unsynchronized interior mutability) are Send — you can hand them to another thread — but not Sync: you can't share them. Raw pointers are neither. Each exception blocks exactly the operation that would cause a race.

№ 02La surpriseThe surprise4 min

On change Rc en Arc… et la donnée reste obstinément en lecture seule.You swap Rc for Arc… and the data stays stubbornly read-only.

Le réflexe pour partager une donnée entre threads vient du Vol 6 : un compteur de références. On tente Rc — et le compilateur refuse avec un message inhabituel, qui parle de Send et de threads, pas de durée de vie. La raison est exactement la distinction du Vol 6 №2 : Rc compte ses références sans atomicité, donc deux threads le manipulant en parallèle corromptraient le compte. Arc, lui, utilise un compteur atomique : il est Send, il traverse. Premier obstacle levé. Mais on découvre alors le second, plus subtil : Arc partage la possession, et plusieurs Arc sont autant d'accès partagés — autant de &. Or le Vol 1 l'a gravé : on n'écrit pas à travers un &. Arc a réglé l'envoi sans rien régler de la mutation. La donnée a traversé ; elle reste figée.The reflex for sharing data across threads comes from Vol 6: a reference count. You try Rc — and the compiler refuses with an unusual message, about Send and threads, not lifetimes. The reason is exactly Vol 6 №2's distinction: Rc counts its references without atomicity, so two threads handling it in parallel would corrupt the count. Arc uses an atomic counter: it's Send, it crosses. First obstacle cleared. But then you meet the second, subtler one: Arc shares ownership, and several Arcs are that many shared accesses — that many &. Yet Vol 1 carved it: you don't write through a &. Arc fixed the sending without fixing anything about mutation. The data crossed; it stays frozen.

Rc ne traverse pas — son compteur n'est pas atomiqueRc doesn't cross — its counter isn't atomic
use std::rc::Rc;
use std::thread;

let compteur = Rc::new(0);
let c = Rc::clone(&compteur);
thread::spawn(move || {
    println!("{}", c);          // ✕ error[E0277]: `Rc<i32>` cannot be sent between
});                             //   threads safely — the trait `Send` is not implemented
// Rc compte ses références SANS atomicité. deux threads clonant/lâchant en même temps
// corromptraient le compteur → double libération ou fuite. le compilateur refuse net.

L'erreur ne dit pas « ça plantera » — elle dit Rc<i32> cannot be sent between threads safely. C'est Send qui parle. Le Vol 6 avait présenté Rc vs Arc comme un choix de performance (atomique = un peu plus cher) ; on voit ici que c'en est aussi un de sûreté vérifiée à la compilation. Rc n'est pas Send, donc il ne peut littéralement pas entrer dans une closure de thread. La course sur le compteur est rendue inexprimable, pas seulement déconseillée.The error doesn't say 'it will crash' — it says Rc<i32> cannot be sent between threads safely. That's Send speaking. Vol 6 presented Rc vs Arc as a performance choice (atomic = slightly costlier); here we see it's also a compile-time-checked safety one. Rc isn't Send, so it literally cannot enter a thread's closure. The race on the counter is made inexpressible, not merely discouraged.

Arc résout l'envoi, pas la mutation : il partage la possession en lecture, et laisse intacte l'interdiction d'écrire à travers un partage.Arc solves the sending, not the mutation: it shares ownership read-only, and leaves intact the ban on writing through a share.
№ 03La mécaniqueThe mechanics5 min

Le Mutex est le borrow check du Vol 1 — qui bloque au lieu de prouver.The Mutex is Vol 1's borrow check — that blocks instead of proving.

Arc<Mutex<T>> se lit comme une phrase : Arc partage la possession entre threads (compteur atomique, Vol 6), Mutex rétablit l'exclusivité de l'accès. Le Mutex tient un verrou ; pour toucher la donnée, un thread appelle lock(), qui bloque jusqu'à ce que le verrou soit libre, puis le prend et rend un MutexGuard — l'unique &mut vers l'intérieur. Tant qu'un thread tient ce guard, les autres lock() attendent. Quand le guard est lâché (sa destruction), le verrou se libère et le thread suivant passe. C'est, trait pour trait, la règle « un seul écrivain » du Vol 1 — mais arbitrée à l'exécution par un verrou, et appliquée en faisant patienter, non en refusant de compiler. Et c'est Sync qui légitime le tout : Mutex<T> est Sync, donc &Mutex peut être partagé entre threads sans danger.Arc<Mutex<T>> reads like a sentence: Arc shares ownership across threads (atomic counter, Vol 6), Mutex restores exclusivity of access. The Mutex holds a lock; to touch the data, a thread calls lock(), which blocks until the lock is free, then takes it and returns a MutexGuard — the sole &mut to the inside. As long as one thread holds that guard, the other lock() calls wait. When the guard is dropped (its destruction), the lock releases and the next thread goes. It is, trait for trait, Vol 1's 'single writer' rule — but arbitrated at runtime by a lock, and enforced by making threads wait, not by refusing to compile. And Sync legitimizes the whole thing: Mutex<T> is Sync, so &Mutex can be shared across threads without danger.

Le verrou : un seul thread tient le &mut, les autres patiententThe lock: one thread holds the &mut, the others wait
T1tient le verrouholds the lock
T2bloquéblocked
T3bloquéblocked
lock()▸file d'attentewait queue
Mutex
&mut data
tenu par 1 threadheld by 1 thread
drop(guard) → le verrou se libère → le suivant passedrop(guard) → lock releases → next goes
Dix threads, un compteur, aucune courseTen threads, one counter, no race
use std::sync::{Arc, Mutex};
use std::thread;

let compteur = Arc::new(Mutex::new(0));       // Arc : partage la possession · Mutex : l'exclusivité
let mut handles = vec![];

for _ in 0..10 {
    let c = Arc::clone(&compteur);            // chaque thread reçoit un clone de l'Arc
    handles.push(thread::spawn(move || {
        let mut n = c.lock().unwrap();        // lock() BLOQUE jusqu'à obtenir l'accès exclusif
        *n += 1;                              // ← un seul thread est ici à la fois
    }));                                      // le guard `n` est lâché ici → le verrou se libère
}
for h in handles { h.join().unwrap(); }
println!("{}", *compteur.lock().unwrap());    // 10, toujours — jamais de data race

Le résultat est toujours 10, jamais 7 ou 9. Sans le Mutex, dix incréments concurrents sur le même entier se marcheraient dessus — lire, ajouter, réécrire, en se chevauchant — et perdraient des comptes : le data race classique. Le verrou sérialise l'accès au point exact où c'est nécessaire (le *n += 1), et seulement là. Remarque la discipline implicite : le guard n est lâché en fin de boucle, automatiquement, par le drop du Vol 1. On ne « déverrouille » jamais à la main — la possession du guard est la durée du verrou.The result is always 10, never 7 or 9. Without the Mutex, ten concurrent increments on the same integer would step on each other — read, add, write back, overlapping — and lose counts: the classic data race. The lock serializes access at the exact point where it's needed (the *n += 1), and only there. Note the implicit discipline: the guard n is dropped at the loop's end, automatically, by Vol 1's drop. You never 'unlock' by hand — the guard's ownership is the lock's duration.

Sync : ce qui rend le partage d'un &mut légitimeSync: what makes sharing a &mut legitimate
// pourquoi &Mutex<T> peut être partagé entre threads : Mutex est Sync
unsafe impl<T: Send> Sync for Mutex<T> {}     // impl MANUELLE : la std prouve elle-même le Sync

// Mutex<T> est Sync (si T: Send) → &Mutex<T> est Send → Arc<Mutex<T>> traverse et se partage.
// le verrou est ce qui REND légitime le partage d'un accès mutable : il garantit, à
// l'exécution, qu'un seul thread tient le &mut à un instant donné. la preuve statique
// du Vol 1 devient une garantie dynamique — exactement comme RefCell, mais qui BLOQUE
// au lieu de paniquer, parce qu'entre threads « attendre son tour » a un sens.
Bloquer plutôt que paniquer.Block rather than panic.

RefCell (Vol 6 №3) et Mutex font la même chose — déplacer le borrow check à l'exécution — mais sanctionnent différemment. RefCell, dans un seul thread, n'a personne à attendre : un second emprunt incompatible est un bug ici et maintenant, donc il panique. Mutex, entre threads, a quelqu'un à attendre : l'autre thread va finir et lâcher le verrou. Bloquer a un sens. Même mécanique, deux sanctions, parce que le contexte diffère. Et c'est un autre détail qui interdit RefCell entre threads : son compteur d'emprunts n'est pas atomique — deux threads qui le modifieraient ensemble feraient une course sur ce compteur même. C'est pourquoi RefCell n'est pas Sync, et Mutex, dont le verrou est synchronisé, l'est.RefCell (Vol 6 №3) and Mutex do the same thing — move the borrow check to runtime — but sanction differently. RefCell, in one thread, has no one to wait for: a second incompatible borrow is a bug here and now, so it panics. Mutex, across threads, has someone to wait for: the other thread will finish and release the lock. Blocking makes sense. Same mechanics, two sanctions, because the context differs. And it's another detail that bars RefCell from crossing threads: its borrow counter isn't atomic — two threads updating it together would race on that very counter. That's why RefCell isn't Sync, while Mutex, whose lock is synchronized, is.

Arc partage la possession, Mutex rétablit l'exclusivité : la règle du Vol 1, arbitrée par un verrou au lieu d'une preuve.Arc shares ownership, Mutex restores exclusivity: Vol 1's rule, arbitrated by a lock instead of a proof.
№ 04Le piègeThe trap4 min

Un gros Mutex « par sécurité » qui sérialise tout — et tue le parallélisme.One big Mutex 'for safety' that serializes everything — and kills the parallelism.

Une fois Arc<Mutex<T>> en main, le réflexe est de tout y mettre : un seul Mutex autour de l'état entier, verrouillé au début de chaque tâche, relâché à la fin. C'est sûr — le compilateur est content, aucun data race. Mais on a oublié pourquoi on avait lancé des threads. Si chaque thread prend le verrou global et le tient pendant tout son travail, alors un seul avance à la fois : les autres font la queue. On a payé le coût du multi-threading (création de threads, synchronisation, complexité) pour obtenir une exécution… séquentielle. Le Mutex ne doit protéger que la section critique — l'instant précis où l'on touche la donnée partagée — et la libérer aussitôt. Mieux : pour un compteur, un atomique se passe de verrou ; pour une donnée surtout lue, un RwLock laisse les lecteurs avancer ensemble.Once Arc<Mutex<T>> is in hand, the reflex is to put everything in it: a single Mutex around the whole state, locked at each task's start, released at its end. It's safe — the compiler is happy, no data race. But you've forgotten why you spawned threads. If every thread takes the global lock and holds it for all its work, then only one advances at a time: the others queue. You paid the cost of multithreading (thread creation, synchronization, complexity) to get… sequential execution. The Mutex should protect only the critical section — the precise moment you touch the shared data — and release it at once. Better: for a counter, an atomic needs no lock; for mostly-read data, an RwLock lets readers advance together.

Le contre-exemple — le verrou global tenu trop longtempsThe counter-example — the global lock held too long
// Le réflexe : un seul gros Mutex autour de TOUT l'état, « par sécurité »
struct Etat { compteur: u64, cache: HashMap<u32, String>, journal: Vec<String> }
let etat = Arc::new(Mutex::new(Etat { /* … */ }));

// dans chaque thread :
let mut e = etat.lock().unwrap();      // ← prend le verrou GLOBAL
e.compteur += 1;                       // …même pour ne toucher qu'un champ
recalculer(&mut e.cache);              // et on le tient pendant TOUT le travail long
e.journal.push(ligne);
// pendant ce temps, les 7 autres threads sont BLOQUÉS sur le même verrou. on a écrit
// du parallèle qui s'exécute en SÉRIE : le Mutex a tué le parallélisme qu'on cherchait.
T1
travailwork
T2
attend le verrouwaits for the lock
travailwork
T3
attend le verrouwaits for the lock
travailwork

↳ un seul avance à la fois → temps total = somme des travaux↳ one advances at a time → total time = sum of the works

Le bon réflexe — verrous fins, sections critiques courtes, atomiquesThe right reflex — fine locks, short critical sections, atomics
// Verrouiller le PLUS PETIT, le PLUS BREF — ou pas du tout
let compteur = Arc::new(AtomicU64::new(0));         // un compteur → un atomique, sans verrou
let cache = Arc::new(RwLock::new(HashMap::new()));  // lecture massive → RwLock (N lecteurs //)

// dans chaque thread :
compteur.fetch_add(1, Ordering::Relaxed);           // incrément atomique, zéro blocage
{
    let c = cache.read().unwrap();                  // plusieurs threads LISENT en parallèle
    utiliser(&c);
}                                                    // section critique réduite au minimum
// chaque donnée a son propre verrou (ou aucun) : les threads avancent vraiment ensemble.
T1
travailwork
T2
travailwork
T3
travailwork

↳ tous avancent ensemble → temps total ≈ un seul travail↳ all advance together → total time ≈ one work

La règle tient en une image : un Mutex est un goulot. Plus la section critique est large et longue, plus le goulot étrangle. La bonne granularité, c'est de verrouiller la plus petite donnée possible, le moins longtemps possible, et de préférer les outils sans verrou quand ils existent — AtomicU64 pour un compteur, RwLock quand les lectures dominent (il autorise N lecteurs simultanés, un seul écrivain). Le but n'est jamais « ajouter de la synchronisation » ; c'est « en mettre juste assez pour que ce soit correct, et pas un instant de plus ».The rule fits in one image: a Mutex is a bottleneck. The wider and longer the critical section, the more it chokes. The right granularity is to lock the smallest possible data, for the shortest possible time, and to prefer lock-free tools when they exist — AtomicU64 for a counter, RwLock when reads dominate (it allows N simultaneous readers, one writer). The goal is never 'add synchronization'; it's 'add just enough to be correct, and not a moment more'.

Le rappel honnête — le deadlockThe honest reminder — the deadlock

Rust empêche le data race, pas le deadlock. Deux verrous pris dans deux ordres opposés par deux threads — A puis B ici, B puis A là — et chacun attend que l'autre lâche : le programme se fige, pour toujours. Ça compile sans broncher. Le borrow checker raisonne sur la possession et les durées de vie, pas sur l'ordre dynamique des prises de verrou. Cette part-là reste à ta charge.Rust prevents the data race, not the deadlock. Two locks taken in two opposite orders by two threads — A then B here, B then A there — and each waits for the other to release: the program freezes, forever. It compiles without a hitch. The borrow checker reasons about ownership and lifetimes, not about the dynamic order of lock acquisitions. That part stays your responsibility.

La discipline anti-deadlockThe anti-deadlock discipline

Trois habitudes suffisent à éviter la quasi-totalité des interblocages. Un : verrouiller toujours dans le même ordre global (si tu prends A puis B quelque part, ne prends jamais B puis A ailleurs). Deux : tenir les verrous le moins longtemps possible — pas d'appel bloquant, pas de I/O, pas de calcul long pendant qu'un verrou est tenu. Trois : ne jamais tenir deux verrous à la fois si tu peux l'éviter. Rust ne te les impose pas ; il te laisse, ici, la responsabilité que tu avais dans tous les langages.Three habits avoid nearly all deadlocks. One: always lock in the same global order (if you take A then B somewhere, never take B then A elsewhere). Two: hold locks as briefly as possible — no blocking call, no I/O, no long computation while a lock is held. Three: never hold two locks at once if you can avoid it. Rust doesn't impose these on you; here, it leaves you the responsibility you had in every language.

Un Mutex est un goulot : verrouille le plus petit, le plus bref — et souviens-toi que Rust t'épargne la course, jamais l'interblocage.A Mutex is a bottleneck: lock the smallest, the briefest — and remember Rust spares you the race, never the deadlock.
№ 05Bilan & éditoWrap-up & editorial3 min

Partager et muter entre threads : deux outils, et une honnêteté.Share and mutate across threads: two tools, and one honesty.

Ce numéro rouvre la porte que le №1 avait laissée fermée. Send et Sync sont les deux traits auto-dérivés qui décident, à la compilation, ce qui peut traverser un thread et ce qui peut y être partagé — la mécanique invisible derrière les refus du langage. Rc échoue (compteur non atomique, pas Send) ; Arc traverse, mais ne partage qu'en lecture. Pour muter, il faut le second outil : Mutex, qui rétablit l'exclusivité à l'exécution en faisant patienter les threads — le borrow check du Vol 1, arbitré par un verrou. Arc<Mutex<T>> est l'assemblage exact : partage de possession plus exclusivité d'accès. Et le piège ferme la boucle : un verrou trop large sérialise tout et tue le parallélisme, quand un atomique ou un RwLock l'aurait préservé. Avec, en filigrane, le seul aveu de tout le volume — Rust empêche le data race, pas le deadlock.This issue reopens the door №1 left shut. Send and Sync are the two auto-derived traits that decide, at compile time, what may cross a thread and what may be shared in one — the invisible machinery behind the language's refusals. Rc fails (non-atomic counter, not Send); Arc crosses, but shares read-only. To mutate, you need the second tool: Mutex, which restores exclusivity at runtime by making threads wait — Vol 1's borrow check, arbitrated by a lock. Arc<Mutex<T>> is the exact assembly: sharing of ownership plus exclusivity of access. And the trap closes the loop: a too-wide lock serializes everything and kills the parallelism, where an atomic or an RwLock would have preserved it. With, underlying it all, the volume's one confession — Rust prevents the data race, not the deadlock.

L'outilThe toolCe qu'il garantit (ou non)What it guarantees (or not)
SendLa possession peut partir vers un autre thread. Auto-dérivé.Ownership can leave for another thread. Auto-derived.
Sync&T peut être partagé entre threads (≡ &T est Send). Auto-dérivé.&T can be shared across threads (≡ &T is Send). Auto-derived.
Arc<T>Le partage de possession entre threads — en lecture seule.Sharing of ownership across threads — read-only.
Mutex<T>L'exclusivité rétablie : un seul &mut à la fois, les autres bloquent.Exclusivity restored: one &mut at a time, the others block.
RwLock · AtomicMoins de contention : N lecteurs //, ou aucune attente du tout.Less contention: N readers in parallel, or no waiting at all.
le deadlockthe deadlockNon garanti : deux verrous en ordre croisé figent le programme. À ta charge.Not guaranteed: two locks in crossed order freeze the program. Your job.
Le partage mutable entre threads coûte un verrou, et la vigilance qu'aucun compilateur ne porte : la course est impossible, l'interblocage non.Shared mutation across threads costs a lock, and the vigilance no compiler carries: the race is impossible, the deadlock isn't.
Mot de l'éditeurFrom the editor

On a appris à partager la mémoire prudemment. Et si on cessait de la partager ?We've learned to share memory carefully. What if we stopped sharing it?

Regarde le chemin parcouru dans ce volume. Le №1 transférait la possession dans un thread, ou la prêtait le temps d'un scope. Le №2 a rouvert le partage mutable — mais au prix d'un verrou, d'une contention à doser, et d'un deadlock qu'aucune preuve ne couvre. La mémoire partagée mutable est puissante, et elle est piégeuse : c'est le terrain où la concurrence reste difficile, même en Rust. D'où une question qui retourne le problème : et si la difficulté venait du partage lui-même ?Look at the path through this volume. №1 transferred ownership into a thread, or lent it for a scope. №2 reopened shared mutation — but at the price of a lock, of contention to balance, and of a deadlock no proof covers. Shared mutable memory is powerful, and it's treacherous: it's the ground where concurrency stays hard, even in Rust. Hence a question that flips the problem: what if the difficulty came from sharing itself?

Vol 7 №3 : les canaux. La devise de la concurrence en Go, que Rust fait sienne : « ne communique pas en partageant la mémoire ; partage la mémoire en communiquant ». Au lieu de plusieurs threads qui se disputent un Arc<Mutex>, un thread envoie un message dans un canal, un autre le reçoit — et la possession de la donnée traverse le canal, transférée par le move du №1. Plus de verrou, plus de section critique, plus de deadlock sur des données : le data race devient impossible non parce qu'on l'interdit, mais parce qu'il n'y a plus rien à partager. On verra mpsc dans la std, crossbeam pour aller plus loin, et le réflexe final du volume : avant Arc<Mutex>, demande-toi si un message ne suffirait pas.Vol 7 №3: channels. Go's concurrency motto, which Rust makes its own: 'don't communicate by sharing memory; share memory by communicating'. Instead of several threads fighting over an Arc<Mutex>, one thread sends a message down a channel, another receives it — and ownership of the data crosses the channel, transferred by №1's move. No more lock, no more critical section, no more deadlock on data: the data race becomes impossible not because we forbid it, but because there's nothing left to share. We'll see mpsc in the std, crossbeam to go further, and the volume's final reflex: before Arc<Mutex>, ask whether a message wouldn't do.