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

Itérateurs & abstractions zéro-coût Iterators & zero-cost abstractions

La chaîne .iter().map().filter().collect() compile vers la même boucle qu'à la main — lisibilité sans surcoût, le zero-cost prouvé plutôt que promis. The .iter().map().filter().collect() chain compiles to the same loop as by hand — readability without overhead, zero-cost proven rather than promised.

AudienceAudience
Dev venant d'un langage à GC, à l'aise avec les generics (Vol 5) Dev from a GC language, comfortable with generics (Vol 5)
Format
Self-paced
ChapitresChapters
5
Date
Juin 2026 Jun 2026
≈ 18 min ●●●○ CoûtItérateursZero-cost
SommaireContents ·
№ 01CadrageFraming3 min

La chaîne d'itérateurs compile vers exactement la boucle qu'on aurait écrite.The iterator chain compiles to exactly the loop you'd have written.

On ouvre le dernier volume, celui du coût. Et la première peur, héritée des langages à GC, vise les itérateurs. En JavaScript, arr.map().filter() crée un tableau intermédiaire à chaque étape, parcouru en entier : deux allocations, deux passes, pour une seule logique. Le réflexe qu'on en garde est de « redescendre à une boucle manuelle » dès qu'il s'agit de performance. En Rust, cette peur est infondée, et c'est le cœur du numéro. La chaîne .iter().map().filter().collect() ne crée aucune collection intermédiaire et compile, après optimisation, vers un code machine équivalent à celui de la boucle écrite à la main. C'est le sens précis de « zéro-coût » : l'abstraction se paie à la compilation (en monomorphisation et inlining, Vol 5), jamais à l'exécution. Tu écris ce qui est lisible ; tu obtiens ce qui est rapide. Les deux ne s'opposent pas.We open the last volume, the one on cost. And the first fear, inherited from GC languages, targets iterators. In JavaScript, arr.map().filter() creates an intermediate array at each step, traversed in full: two allocations, two passes, for one logic. The reflex you keep is to 'drop down to a manual loop' the moment performance matters. In Rust, that fear is unfounded, and it's the heart of the issue. The .iter().map().filter().collect() chain creates no intermediate collection and compiles, after optimization, to machine code equivalent to the hand-written loop. That's the precise meaning of 'zero-cost': the abstraction is paid at compile time (in monomorphization and inlining, Vol 5), never at runtime. You write what's readable; you get what's fast. The two aren't opposed.

Deux sources, un seul code machineTwo sources, one machine code
chaîne d'itérateursiterator chain
.iter().filter(..).map(..).collect()
rustc▸opt + inlineopt + inline
code machine équivalentequivalent machine code
1 boucle
0 alloc intermédiaire0 intermediate alloc
◂rustc
boucle à la mainmanual loop
for n in &v { if .. { push(..)} }
La chaîne et la boucle, côte à côteThe chain and the loop, side by side
// la chaîne lisible
let pairs_doubles: Vec<i32> = nombres.iter()
    .filter(|&&n| n % 2 == 0)
    .map(|&n| n * 2)
    .collect();

// la boucle « à la main » qu'on écrirait par peur du surcoût
let mut pairs_doubles = Vec::new();
for &n in &nombres {
    if n % 2 == 0 { pairs_doubles.push(n * 2); }
}
// après optimisation, les deux produisent un code machine ÉQUIVALENT. la chaîne ne coûte rien
// de plus — c'est ça, le « zéro-coût » : payé à la compilation, pas à l'exécution.

Lis les deux versions : même résultat, même travail. La différence est uniquement à l'écriture — l'une déclare l'intention (filtrer, doubler, collecter), l'autre détaille la mécanique. À la compilation, elles convergent : le compilateur déroule la chaîne en une boucle unique, sans allocation intermédiaire ni appel de fonction résiduel. Choisir la chaîne n'est donc pas un compromis « lisibilité contre vitesse » — c'est les deux à la fois. Le seul vrai compromis serait de réécrire la boucle à la main en croyant gagner quelque chose : on n'y gagne rien, on perd en clarté.Read both versions: same result, same work. The difference is only in the writing — one declares the intent (filter, double, collect), the other spells out the mechanics. At compile time, they converge: the compiler unrolls the chain into a single loop, with no intermediate allocation or residual function call. Choosing the chain isn't a 'readability vs speed' trade-off — it's both at once. The only real trade-off would be rewriting the loop by hand believing you gain something: you gain nothing, you lose clarity.

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

« Cette abstraction a-t-elle un coût à l'exécution, ou seulement à la compilation ? » Pour les itérateurs, la réponse est nette : aucun coût à l'exécution. Le bon réflexe est donc d'écrire la chaîne la plus claire, sans culpabilité de performance, et de ne descendre à la boucle manuelle que si un profil le prouve nécessaire — ce qui, pour les itérateurs, n'arrive presque jamais. C'est la règle de tout le volume : on n'optimise pas sur une intuition, on optimise sur une mesure.'Does this abstraction cost at runtime, or only at compile time?' For iterators, the answer is clear: no runtime cost. The right reflex is thus to write the clearest chain, without performance guilt, and to drop to the manual loop only if a profile proves it necessary — which, for iterators, almost never happens. It's the whole volume's rule: you don't optimize on a hunch, you optimize on a measurement.

« Zéro-coût », la définition de Stroustrup'Zero-cost', Stroustrup's definition

La formule vient du C++ : « ce que tu n'utilises pas, tu ne le paies pas ; et ce que tu utilises, tu ne pourrais pas mieux le coder à la main ». Rust en fait un principe central. Une abstraction zéro-coût n'est pas magiquement gratuite — elle est gratuite parce que le compilateur l'efface : après monomorphisation et inlining, il ne reste aucune trace de la couche d'abstraction dans le binaire. Attention au contre-sens : « zéro-coût » parle du coût à l'exécution, pas du temps de compilation (que la monomorphisation, elle, allonge bel et bien).The phrase comes from C++: 'what you don't use, you don't pay for; and what you do use, you couldn't hand-code better'. Rust makes it a central principle. A zero-cost abstraction isn't magically free — it's free because the compiler erases it: after monomorphization and inlining, no trace of the abstraction layer remains in the binary. Beware the misreading: 'zero-cost' is about runtime cost, not compile time (which monomorphization does lengthen).

№ 02La surpriseThe surprise4 min

.map() ne fait rien : il décrit un calcul, et n'alloue aucun tableau..map() does nothing: it describes a computation, and allocates no array.

La raison pour laquelle la chaîne ne coûte rien tient en un mot : elle est paresseuse. C'est l'écho exact de la leçon du Vol 8 sur les Futures — appeler .map() ou .filter() n'exécute aucun travail, ne parcourt rien, n'alloue rien. Cela construit simplement un nouvel itérateur : une struct qui se souvient « j'appliquerai cette fonction quand on me tirera des éléments ». Tant qu'aucun consommateur ne demande de valeurs — collect, sum, for, count — la chaîne reste inerte. Le compilateur va jusqu'à t'avertir si tu oublies de consommer : « iterators are lazy and do nothing unless consumed ». Conséquence directe et heureuse : il n'y a pas de tableaux intermédiaires. Les adaptateurs empilés ne créent pas une collection par étape ; ils fusionnent en une seule passe qui tire chaque élément une fois, à travers toute la chaîne.The reason the chain costs nothing fits in one word: it's lazy. It's the exact echo of Vol 8's lesson on Futures — calling .map() or .filter() runs no work, traverses nothing, allocates nothing. It simply builds a new iterator: a struct that remembers 'I'll apply this function when elements are pulled from me'. As long as no consumer asks for values — collect, sum, for, count — the chain stays inert. The compiler even warns you if you forget to consume: 'iterators are lazy and do nothing unless consumed'. Direct and happy consequence: there are no intermediate arrays. Stacked adapters don't create one collection per step; they fuse into a single pass that pulls each element once, through the whole chain.

Sans consommateur, la chaîne ne tourne pasWithout a consumer, the chain doesn't run
let v = vec![1, 2, 3];

v.iter().map(|x| println!("{x}"));   // ✕ n'affiche RIEN
//   warning: unused `Map` that must be used
//   note: iterators are lazy and do nothing unless consumed

// .map() ne fait qu'EMBALLER : il rend un Map<…>, une description, pas une exécution.
// rien ne tourne tant qu'un consommateur (collect, sum, for, count…) ne TIRE pas les éléments.

« Iterators are lazy and do nothing unless consumed » — la même phrase, presque mot pour mot, que pour les Future du Vol 8. Et la même logique : un adaptateur est une valeur (une struct), pas une exécution. La construire ne déclenche rien ; seul un consommateur tire les éléments et fait tourner toute la chaîne, élément par élément. C'est cette paresse qui rend la fusion possible : puisque rien ne se matérialise avant le consommateur final, le compilateur peut tout fondre en une passe unique. Lazy par conception, fusionné en conséquence.'Iterators are lazy and do nothing unless consumed' — the same sentence, almost word for word, as for Vol 8's Futures. And the same logic: an adapter is a value (a struct), not an execution. Building it triggers nothing; only a consumer pulls the elements and runs the whole chain, element by element. That laziness is what makes fusion possible: since nothing materializes before the final consumer, the compiler can melt it all into a single pass. Lazy by design, fused as a result.

Un adaptateur est une description, pas une exécution : rien ne tourne, rien ne s'alloue, tant qu'un consommateur ne tire pas la chaîne.An adapter is a description, not an execution: nothing runs, nothing allocates, until a consumer pulls the chain.
№ 03La mécaniqueThe mechanics5 min

Des structs imbriquées, que la monomorphisation aplatit en une boucle.Nested structs, which monomorphization flattens into one loop.

Comment une chaîne d'appels peut-elle ne rien coûter ? Le secret est que chaque adaptateur est une struct qui en enveloppe une autre. .filter(p) ne filtre rien : il rend un Filter qui détient l'itérateur précédent et le prédicat. .map(f) rend un Map qui détient ce Filter et la fonction. La chaîne entière est donc un type imbriqué — Map<Filter<Iter>, F> — et tout repose sur une seule méthode, next, que chaque couche implémente en appelant celle de la couche en dessous. Quand collect tire les éléments, l'appel descend en cascade à travers les structs. C'est ici qu'intervient la mécanique du Vol 5 : la monomorphisation génère du code concret pour chaque type, et l'inlining fond les next() imbriqués les uns dans les autres. Il ne reste, dans le binaire, qu'une boucle plate — aucune trace des couches. L'abstraction n'est pas exécutée puis optimisée : elle est effacée.How can a chain of calls cost nothing? The secret is that each adapter is a struct wrapping another. .filter(p) filters nothing: it returns a Filter holding the previous iterator and the predicate. .map(f) returns a Map holding that Filter and the function. The whole chain is thus a nested type — Map<Filter<Iter>, F> — and everything rests on a single method, next, which each layer implements by calling the one below. When collect pulls the elements, the call cascades down through the structs. Here's where Vol 5's machinery enters: monomorphization generates concrete code for each type, and inlining melts the nested next()s into one another. All that remains, in the binary, is one flat loop — no trace of the layers. The abstraction isn't executed then optimized: it's erased.

Les couches imbriquées — puis aplaties par le compilateurThe nested layers — then flattened by the compiler
Map<…, F>
next → applique f
Filter<…, P>
next → teste p
Iter<i32>
next → élément brut
structs imbriquées · next() en cascadenested structs · cascading next()
mono + inlinemono + inline▸
après compilationafter compilation
une boucle plateone flat loop
0 appel · 0 indirection0 call · 0 indirection
Tout repose sur un seul next()Everything rests on a single next()
trait Iterator {
    type Item;
    fn next(&mut self) -> Option<Self::Item>;   // « donne l'élément suivant, ou None »
}
// tout le reste — map, filter, take, zip, sum… — est construit SUR ce seul next().
// chaque adaptateur implémente next() en appelant le next() de l'itérateur qu'il enveloppe.

Le trait Iterator n'a qu'une méthode requise : next. Les dizaines d'adaptateurs (map, filter, take, zip…) sont des méthodes par défaut bâties dessus, chacune renvoyant une struct qui redéfinit next en termes du next sous-jacent. Cette uniformité est ce qui rend l'inlining si efficace : il n'y a qu'une forme d'appel à fondre, répétée à chaque couche. Le compilateur la voit, la déplie, et la cascade de next devient une boucle unique avec le corps de chaque adaptateur inséré en ligne.The Iterator trait has only one required method: next. The dozens of adapters (map, filter, take, zip…) are default methods built on it, each returning a struct that redefines next in terms of the underlying next. This uniformity is what makes inlining so effective: there's only one call shape to melt, repeated at each layer. The compiler sees it, unfolds it, and the cascade of next becomes a single loop with each adapter's body inserted inline.

La chaîne, telle que le compilateur la voitThe chain, as the compiler sees it
// .map() et .filter() ne « font » rien : ils EMBALLENT l'itérateur précédent dans une struct
let chaine = nombres.iter().filter(pred).map(f);
//   type réel ≈  Map<Filter<Iter<i32>, P>, F>     ← des structs imbriquées

// collect() appelle .next() en boucle ; chaque .next() traverse les couches :
//   Map::next  →  Filter::next  →  Iter::next,  puis applique f
// monomorphisation (Vol 5) + inlining : le compilateur APLATIT toutes ces couches en
// une seule boucle, sans appel de fonction ni indirection. l'abstraction est EFFACÉE.
Le revers : dyn Iterator n'est PAS zéro-coût.The flip side: dyn Iterator is NOT zero-cost.

C'est l'arbitrage du Vol 5, appliqué ici. Les itérateurs sont à dispatch statique : leur type concret est connu, donc next() s'inline. Mais dès qu'on les boxe en Box<dyn Iterator>, on passe au dispatch dynamique : chaque next() devient un appel via vtable, qu'on ne peut plus inliner — l'abstraction redevient payante, un appel indirect par élément. Le zéro-coût n'est donc pas une propriété magique des itérateurs ; c'est une propriété de la monomorphisation. L'effacer (avec dyn) rétablit le coût. À réserver aux cas où l'hétérogénéité l'exige vraiment.It's Vol 5's trade-off, applied here. Iterators are statically dispatched: their concrete type is known, so next() inlines. But the moment you box them as Box<dyn Iterator>, you switch to dynamic dispatch: each next() becomes a vtable call, no longer inlinable — the abstraction becomes paid again, one indirect call per element. Zero-cost isn't a magic property of iterators; it's a property of monomorphization. Erasing it (with dyn) restores the cost. Reserve it for cases where heterogeneity genuinely demands it.

La chaîne est un empilement de structs ; la monomorphisation et l'inlining l'aplatissent — jusqu'à effacer l'abstraction du binaire.The chain is a stack of structs; monomorphization and inlining flatten it — until the abstraction is erased from the binary.
№ 04Le piègeThe trap4 min

L'abstraction est gratuite — mais la matérialiser au mauvais endroit ne l'est pas.The abstraction is free — but materializing it at the wrong spot isn't.

Le zéro-coût concerne les adaptateurs paresseux — map, filter, take — qui fusionnent en une passe. Mais un consommateur, lui, fait un vrai travail : collect alloue une collection et la remplit. C'est là qu'on se piège, à l'inverse de la peur du chapitre 2 : non en craignant un coût qui n'existe pas, mais en en créant un sans le voir. Le cas typique : glisser un collect au milieu d'une chaîne, pour « y voir clair » ou par habitude, puis reparcourir le Vec ainsi produit. On vient de matérialiser une collection intermédiaire — exactement ce que la paresse évitait — et de parcourir les données deux fois au lieu d'une. La chaîne avait raison d'être paresseuse ; on a brisé la fusion de nos propres mains. Le coût n'est pas dans l'abstraction : il est dans le fait de la matérialiser trop tôt.Zero-cost concerns the lazy adapters — map, filter, take — that fuse into one pass. But a consumer does real work: collect allocates a collection and fills it. That's where you trap yourself, the inverse of chapter 2's fear: not by fearing a cost that doesn't exist, but by creating one without seeing it. The typical case: slipping a collect into the middle of a chain, 'to see clearly' or out of habit, then re-traversing the resulting Vec. You've just materialized an intermediate collection — exactly what laziness avoided — and traversed the data twice instead of once. The chain was right to be lazy; you broke the fusion with your own hands. The cost isn't in the abstraction: it's in materializing it too early.

Un collect de trop casse la fusion en deux passesOne collect too many breaks the fusion into two passes
avec collect au milieuwith a mid-chain collect
filter
lazylazy
▸
collect → Vec
allocalloc
▸
iter (2ᵉ passe)
passepass
▸
map
lazylazy
▸
collect
allocalloc

2 allocations · 2 passes complètes2 allocations · 2 full passes

paresseux de bout en boutlazy end to end
filter
lazylazy
▸
map
lazylazy
▸
collect
allocalloc

1 allocation · 1 passe fusionnée1 allocation · 1 fused pass

Le contre-exemple — le collect au milieu de la chaîneThe counter-example — the mid-chain collect
let resultat: Vec<i32> = nombres.iter()
    .filter(|&&n| n > 0)
    .collect::<Vec<&i32>>()    // ✕ matérialise un Vec intermédiaire : 1 allocation + 1 passe
    .iter()                    //   …qu'on reparcourt aussitôt
    .map(|&&n| n * 2)
    .collect();
// le collect du milieu CASSE la fusion : on alloue un Vec qu'on jette, et on parcourt deux fois.
Le bon réflexe — paresseux de bout en bout, un seul collectThe right reflex — lazy end to end, a single collect
let resultat: Vec<i32> = nombres.iter()
    .filter(|&&n| n > 0)
    .map(|&n| n * 2)           // ✓ la chaîne reste PARESSEUSE de bout en bout
    .collect();                // un seul collect, à la fin : une passe, une allocation
// règle : un seul point de matérialisation, le plus tard possible. collect au milieu = stop.

La règle est limpide : un seul point de matérialisation, le plus tard possible. Garde la chaîne paresseuse jusqu'au consommateur final ; n'appelle collect que lorsque tu as réellement besoin de la collection (pour la stocker, la retourner, la parcourir plusieurs fois). Tout collect intermédiaire est un signal d'alarme : ou bien il est inutile et casse la fusion, ou bien il y a une vraie raison — et alors elle doit être explicite, pas accidentelle. La paresse fait le bon choix par défaut ; c'est en la court-circuitant qu'on paie.The rule is clear: a single materialization point, as late as possible. Keep the chain lazy until the final consumer; only call collect when you genuinely need the collection (to store it, return it, traverse it several times). Any intermediate collect is an alarm signal: either it's useless and breaks the fusion, or there's a real reason — and then it must be explicit, not accidental. Laziness makes the right choice by default; it's by short-circuiting it that you pay.

Et l'inverse : ne réécris pas à la mainAnd the inverse: don't rewrite by hand

Le piège symétrique, et le plus courant dans ce volume : réécrire une chaîne propre en boucle manuelle « pour la performance ». On a vu (chapitre 1) qu'elles compilent à l'identique — le gain est nul, la perte de lisibilité réelle. C'est l'optimisation prématurée à l'état pur : sacrifier la clarté pour un gain imaginé. La règle de tout le Vol 9 tranche : on n'optimise qu'avec un profil en main. Sans mesure qui pointe la chaîne comme coupable — ce qui n'arrive presque jamais — on garde la version lisible.The symmetric trap, and the most common in this volume: rewriting a clean chain as a manual loop 'for performance'. We saw (chapter 1) they compile identically — the gain is nil, the readability loss real. It's premature optimization in its purest form: sacrificing clarity for an imagined gain. The whole of Vol 9's rule decides it: you optimize only with a profile in hand. Without a measurement pointing at the chain as culprit — which almost never happens — you keep the readable version.

collect n'est pas le seul consommateurcollect isn't the only consumer

Souvent, le bon réflexe est de ne pas collecter du tout. Si tu n'as besoin que d'une somme, d'un compte, d'un max, d'un « existe-t-il un élément tel que… », d'un premier match — sum(), count(), max(), any(), find() consomment la chaîne sans jamais allouer de collection. De même, for x in chaine itère sans collecter. Demande-toi toujours : ai-je besoin de la collection, ou seulement d'un résultat agrégé ? Dans le second cas, collect est non seulement inutile mais coûteux.Often, the right reflex is not to collect at all. If you only need a sum, a count, a max, an 'is there an element such that…', a first match — sum(), count(), max(), any(), find() consume the chain without ever allocating a collection. Likewise, for x in chain iterates without collecting. Always ask: do I need the collection, or only an aggregated result? In the latter case, collect is not only useless but costly.

Le coût n'est pas dans la chaîne, il est dans le collect de trop : matérialise une seule fois, le plus tard possible — et seulement si tu en as besoin.The cost isn't in the chain, it's in the collect too many: materialize once, as late as possible — and only if you need it.
№ 05Bilan & éditoWrap-up & editorial2 min

Lisible et rapide : l'abstraction zéro-coût n'est pas un compromis.Readable and fast: the zero-cost abstraction isn't a trade-off.

Ce numéro ouvre le dernier volume en désamorçant la première peur du coût. La chaîne .iter().map().filter().collect() compile vers la même boucle qu'à la main — aucune collection intermédiaire, aucune indirection. La raison est double : les adaptateurs sont paresseux (ils décrivent, ne calculent pas, et fusionnent en une passe), et ils reposent sur une seule méthode next() que la monomorphisation et l'inlining (Vol 5) aplatissent jusqu'à effacer l'abstraction du binaire. Le revers est honnête : dyn Iterator rétablit le coût, et un collect glissé au milieu d'une chaîne matérialise un Vec qu'on jette. Le coût n'est jamais dans l'adaptateur paresseux ; il est dans ce qu'on matérialise, et dans l'optimisation prématurée qui réécrit à la main ce qui était déjà optimal.This issue opens the last volume by defusing the first fear of cost. The .iter().map().filter().collect() chain compiles to the same loop as by hand — no intermediate collection, no indirection. The reason is twofold: adapters are lazy (they describe, don't compute, and fuse into one pass), and they rest on a single next() method that monomorphization and inlining (Vol 5) flatten until the abstraction is erased from the binary. The flip side is honest: dyn Iterator restores the cost, and a collect slipped mid-chain materializes a Vec you throw away. The cost is never in the lazy adapter; it's in what you materialize, and in the premature optimization that rewrites by hand what was already optimal.

La notionThe notionCe qu'il faut en retenirWhat to remember
adaptateurs (lazy)adapters (lazy)map, filter… décrivent un calcul ; rien ne tourne sans consommateur.map, filter… describe a computation; nothing runs without a consumer.
la fusionfusionPas de collection intermédiaire : une seule passe sur les données.No intermediate collection: a single pass over the data.
mono + inliningmono + inliningAplatit les structs imbriquées en une boucle plate (Vol 5).Flattens the nested structs into one flat loop (Vol 5).
collectMatérialise : alloue. Un seul, à la fin — jamais au milieu.Materializes: allocates. Just one, at the end — never mid-chain.
dyn IteratorCasse le zéro-coût : un appel via vtable par next() (Vol 5).Breaks zero-cost: a vtable call per next() (Vol 5).
Le zéro-coût n'est pas magique : l'abstraction est effacée à la compilation. Écris la chaîne lisible — c'est aussi la rapide.Zero-cost isn't magic: the abstraction is erased at compile time. Write the readable chain — it's also the fast one.
Mot de l'éditeurFrom the editor

Une abstraction effacée ne coûte rien. Mais .clone(), lui, alloue.An erased abstraction costs nothing. But .clone() allocates.

On a vu une abstraction qui ne coûte rien parce que le compilateur l'efface. C'est rassurant — et c'est précisément ce qui rend le numéro suivant nécessaire, par contraste. Car toutes les opérations ne s'effacent pas. Certaines font un vrai travail, à l'exécution, et le plus discret d'entre eux a un nom familier : .clone(). On l'ajoute si souvent « pour faire taire le compilateur » — un emprunt qui gêne, un move qui dérange, hop, un .clone() et ça passe. Mais là où l'itérateur ne coûtait rien, le clone, lui, copie : il alloue, il duplique, il recopie des octets. Et contrairement à l'abstraction zéro-coût, ce coût-là ne s'évapore pas à la compilation.We've seen an abstraction that costs nothing because the compiler erases it. It's reassuring — and it's precisely what makes the next issue necessary, by contrast. Because not all operations get erased. Some do real work, at runtime, and the most discreet of them has a familiar name: .clone(). We add it so often 'to silence the compiler' — an awkward borrow, a bothersome move, hop, a .clone() and it compiles. But where the iterator cost nothing, the clone copies: it allocates, it duplicates, it copies bytes. And unlike the zero-cost abstraction, that cost doesn't evaporate at compile time.

Vol 9 №2 : clone, Cow & emprunter au lieu de copier. On verra le réflexe .clone() pour ce qu'il est — souvent le signe qu'on n'a pas su exprimer un emprunt, et qu'on paie une allocation pour contourner le borrow checker plutôt que de l'écouter. On verra quand cloner est légitime (et c'est parfois le cas), et l'outil qui tranche entre emprunter et posséder selon les besoins : Cow, « clone on write », qui emprunte tant que possible et ne copie qu'au moment où il le faut vraiment. Le coût, cette fois, ne sera pas effaçable — il faudra le choisir, en connaissance de cause.Vol 9 №2: clone, Cow & borrowing instead of copying. We'll see the .clone() reflex for what it is — often the sign you couldn't express a borrow, and you're paying an allocation to work around the borrow checker rather than listen to it. We'll see when cloning is legitimate (and it sometimes is), and the tool that arbitrates between borrowing and owning as needed: Cow, 'clone on write', which borrows as long as possible and copies only when it truly must. The cost, this time, won't be erasable — it'll have to be chosen, knowingly.