Le mot-clé ne reste pas où on le pose : async remonte toute la pile d'appels.The keyword doesn't stay put: async climbs the whole call stack.
Le numéro précédent a regardé un Future seul, comme s'il vivait isolé. Il n'en est rien : appeler de l'async a une conséquence qui déborde la fonction. Pour obtenir la valeur d'un Future, on l'await ; mais .await n'est permis que dans un contexte async. Donc une fonction qui await devient elle-même async, et celle qui l'appelle aussi, et ainsi de suite jusqu'au main. C'est ce qu'on appelle le « problème des fonctions colorées » : il existe deux couleurs de fonctions, synchrone et asynchrone, et la couleur async se propage de proche en proche dès qu'on l'introduit quelque part. On ne peut pas appeler simplement une async fn depuis du code synchrone — les deux mondes ne se composent pas librement. C'est la friction promise à la fin du №1 : la couleur a un prix, et il se paie sur toute la chaîne d'appels.The previous issue looked at a Future alone, as if it lived in isolation. It doesn't: calling async has a consequence that overflows the function. To get a Future's value, you await it; but .await is only allowed in an async context. So a function that awaits becomes async itself, and its caller too, and so on up to main. This is the 'colored functions problem': there are two colors of function, synchronous and asynchronous, and the async color propagates step by step the moment you introduce it somewhere. You can't simply call an async fn from synchronous code — the two worlds don't compose freely. It's the friction promised at the end of №1: color has a price, and it's paid across the whole call chain.
▲ la couleur remonte : impossible de garder un appelant synchrone au-dessus d'un .await▲ the color climbs: impossible to keep a synchronous caller above an .await
async fn lire_config() -> Config { /* … .await … */ } async fn demarrer() -> Config { // ← DOIT être async : elle .await lire_config let cfg = lire_config().await; valider(cfg) } #[tokio::main] async fn main() { // ← async aussi, remontée jusqu'à la racine let cfg = demarrer().await; } // la couleur remonte : quiconque .await de l'async DOIT être async lui-même (ou ouvrir un runtime).
Suis la couleur de bas en haut. lire_config est async ; demarrer l'.await, donc demarrer est forcée d'être async ; main l'.await à son tour, donc main est async — et c'est #[tokio::main] qui lui donne le runtime nécessaire. Une seule fonction asynchrone au fond de la pile, et toute la chaîne au-dessus se teinte. Ce n'est pas un défaut d'implémentation : c'est structurel. Le chapitre 3 montrera pourquoi la frontière est réelle, et non un caprice du langage.Follow the color bottom-up. lire_config is async; demarrer .awaits it, so demarrer is forced to be async; main .awaits it in turn, so main is async — and #[tokio::main] gives it the necessary runtime. A single async function at the bottom of the stack, and the whole chain above takes the tint. It's not an implementation flaw: it's structural. Chapter 3 will show why the boundary is real, not a language whim.
« Où passe la frontière sync / async dans mon code — et qu'est-ce que ça coûte de la franchir ? » L'async n'est pas gratuit : il colore, il contraint, et il fait ressurgir Send dès qu'une tâche traverse des threads. Le bon réflexe n'est pas de tout teindre en async « au cas où », mais de savoir précisément quelle partie du programme est asynchrone, où se trouve le pont avec le monde synchrone, et ce que l'on tient à travers chaque .await.'Where does the sync / async boundary run in my code — and what does crossing it cost?' Async isn't free: it colors, it constrains, and it brings back Send the moment a task crosses threads. The right reflex isn't to tint everything async 'just in case', but to know precisely which part of the program is asynchronous, where the bridge to the synchronous world sits, and what you hold across each .await.
L'image vient d'un billet célèbre de Bob Nystrom (« What Color is Your Function? »), écrit à propos de JavaScript. L'idée : dans un langage où l'async est un mode d'appel distinct, chaque fonction porte une « couleur », et les règles d'appel entre couleurs sont asymétriques et pénibles — le rouge ne peut pas appeler le bleu sans cérémonie. Rust hérite de ce problème, syntaxe JS oblige. La différence, c'est qu'en Rust la couleur est honnête : elle est inscrite dans le type (Future), vérifiée à la compilation, jamais silencieuse.The image comes from a famous post by Bob Nystrom ('What Color is Your Function?'), written about JavaScript. The idea: in a language where async is a distinct calling mode, each function carries a 'color', and the calling rules between colors are asymmetric and painful — red can't call blue without ceremony. Rust inherits this problem, JS syntax oblige. The difference is that in Rust the color is honest: it's written in the type (Future), checked at compile time, never silent.