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

Threads & closures move Threads & move closures

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.

AudienceAudience
Dev maîtrisant la possession et les emprunts (Vol 1, Vol 2) Dev fluent in ownership and borrowing (Vol 1, Vol 2)
Format
Self-paced
ChapitresChapters
5
Date
Juin 2026 Jun 2026
≈ 18 min ●●●○ ConcurrenceThreadsmove

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

thread::spawn lance un fil qui peut survivre à la fonction qui l'a créé.thread::spawn launches a thread that can outlive the function that created it.

On ouvre le Vol 7 sur la promesse la plus citée de Rust : la « concurrence sans peur ». Le slogan cache une vérité simple — ce n'est pas un nouveau mécanisme, c'est la règle du Vol 1 (aliasing XOR mutation) projetée sur un nouveau terrain : plusieurs threads. thread::spawn prend une closure et la lance sur un fil d'exécution séparé, qui tourne en parallèle du reste. Mais ce fil a une propriété déstabilisante : il peut continuer à tourner après que la fonction qui l'a lancé soit revenue. Tout ce qu'il touche par référence risque alors de pointer vers une donnée déjà détruite — le use-after-free du Vol 2, mais entre threads. C'est pourquoi la closure d'un thread doit, le plus souvent, emporter ses données avec elle : le mot-clé move.We open Vol 7 on Rust's most-quoted promise: 'fearless concurrency'. The slogan hides a simple truth — it's not a new mechanism, it's Vol 1's rule (aliasing XOR mutation) projected onto new ground: several threads. thread::spawn takes a closure and runs it on a separate thread of execution, in parallel with the rest. But that thread has an unsettling property: it can keep running after the function that launched it has returned. Anything it touches by reference then risks pointing at already-destroyed data — Vol 2's use-after-free, but across threads. That's why a thread's closure must, most often, take its data with it: the move keyword.

Un thread, sa closure move, et le join qui l'attendA thread, its move closure, and the join that waits for it
use std::thread;

let salutation = String::from("bonjour");

let handle = thread::spawn(move || {        // `move` : la closure EMPORTE `salutation`
    println!("{salutation} depuis le thread");
});                                         // le thread tourne EN PARALLÈLE du main

handle.join().unwrap();                     // …et on ATTEND sa fin avant de continuer.
// sans ce join(), main pourrait se terminer le premier — et tout le programme s'arrêter,
// thread compris, avant qu'il n'ait rien affiché.
Pourquoi le thread est dangereux par défautWhy the thread is dangerous by default
main()
spawn → exécutionspawn → running
bloqué sur join()blocked on join()
thread
tourne EN PARALLÈLE → finruns IN PARALLEL → end

⚠ sans join(), main pourrait franchir la fin de scope pendant que le thread tourne — toute référence empruntée pointerait alors vers du vide.⚠ without join(), main could cross its scope end while the thread runs — any borrowed reference would then point at nothing.

Le fil lancé vit sa propre vie. Le main (ou n'importe quelle fonction) peut atteindre la fin de son scope — et détruire ses variables locales — pendant que le thread tourne encore. Si la closure du thread tenait une référence vers l'une de ces variables, cette référence pointerait vers du vide. Le compilateur ne peut pas prouver l'ordre des deux fins : il doit donc refuser tout emprunt qu'il ne peut garantir. La solution par défaut n'est pas de garder la référence — c'est de transférer la possession dans le thread, pour qu'il n'y ait plus aucune référence à faire survivre.The launched thread lives its own life. main (or any function) can reach the end of its scope — and destroy its locals — while the thread is still running. If the thread's closure held a reference to one of those variables, that reference would point at nothing. The compiler can't prove the order of the two endings: so it must refuse any borrow it can't guarantee. The default solution isn't to keep the reference — it's to transfer ownership into the thread, so there's no reference left to keep alive.

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

« Cette donnée traverse-t-elle un thread — et qu'est-ce que move lui transfère exactement ? » Avant de lancer un fil, on se demande qui possédera ce qu'il touche. La règle « aliasing XOR mutation » ne s'arrête pas à la frontière du thread : c'est elle qui décide ce qui peut être déplacé, prêté, ou partagé — et à quel prix. Le data race n'est pas un bug qu'on attrape par un test : c'est un programme qui ne compile pas.'Does this data cross into a thread — and what exactly does move transfer to it?' Before launching a thread, ask who will own what it touches. The 'aliasing XOR mutation' rule doesn't stop at the thread boundary: it's what decides what may be moved, lent, or shared — and at what price. A data race isn't a bug you catch with a test: it's a program that doesn't compile.

join, ou le thread orphelinjoin, or the orphan thread

thread::spawn rend un JoinHandle. L'ignorer détache le thread : il tourne sans qu'on l'attende, et meurt brutalement si main se termine avant lui. handle.join() bloque jusqu'à ce que le thread finisse et récupère ce que sa closure a retourné (ou l'erreur s'il a paniqué). Retenir le handle et le joindre, c'est reprendre la main sur l'ordre des fins — exactement ce que le compilateur nous demandait de garantir.thread::spawn returns a JoinHandle. Ignoring it detaches the thread: it runs unawaited, and dies abruptly if main ends first. handle.join() blocks until the thread finishes and recovers what its closure returned (or the error if it panicked). Holding the handle and joining it means regaining control over the order of endings — exactly what the compiler asked us to guarantee.

🔒

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