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

'static & le combat contre le borrow checker 'static & the fight against the borrow checker

Face à une erreur de lifetime, le débutant annote plus fort jusqu'à abandonner. Le bon réflexe : si les lifetimes deviennent ingérables, c'est le design de possession qui est faux. Facing a lifetime error, the beginner annotates harder until they give up. The right reflex: if lifetimes become unmanageable, it's the ownership design that's wrong.

AudienceAudience
Dev maîtrisant lifetimes et élision (Vol 2 №1–2) Dev fluent in lifetimes and elision (Vol 2 №1–2)
Format
Self-paced
ChapitresChapters
5
Date
Juin 2026 Jun 2026
≈ 18 min ●●●○ Lifetimes'staticDesign
SommaireContents ·
№ 01CadrageFraming3 min

La spirale : annoter plus fort, jusqu'à 'static, jusqu'à abandonner.The spiral: annotate harder, up to 'static, until you give up.

Le numéro précédent a donné les trois règles d'élision et le moyen d'écrire une annotation juste. Mais il existe un point au-delà duquel annoter ne corrige plus rien — où chaque 'a ajouté déplace l'erreur sans la résoudre. Le débutant y répond en annotant plus fort, jusqu'au coup de désespoir : coller 'static partout. C'est presque toujours le mauvais geste, et il dit quelque chose de précis — le problème n'est pas dans les durées, il est dans la possession.The previous issue gave the three elision rules and how to write a correct annotation. But there's a point beyond which annotating fixes nothing — where each added 'a moves the error without resolving it. The beginner responds by annotating harder, up to the act of despair: slapping 'static everywhere. It's almost always the wrong move, and it says something precise — the problem isn't in the durations, it's in ownership.

La spirale d'annotation — chaque ligne « répond » à l'erreur précédenteThe annotation spiral — each line 'answers' the previous error
fn dernier<'a>(v: &'a Vec<&'a str>) -> &'a str { /* ... */ }
// erreur ailleurs → on ajoute une durée
fn dernier<'a, 'b>(v: &'a Vec<&'b str>) -> &'b str { /* ... */ }
// erreur encore → on lie les deux
fn dernier<'a, 'b: 'a>(v: &'a Vec<&'b str>) -> &'b str { /* ... */ }
// erreur toujours → le réflexe fatal :
fn dernier(v: &Vec<&'static str>) -> &'static str { /* ... */ }
//                       ^^^^^^^ « vit pour toujours » — la fausse sortie
Ce que 'static signifie vraimentWhat 'static really means

'static n'est pas un mot magique pour « faire taire l'erreur » : c'est la durée la plus longue possible — la donnée vit jusqu'à la fin du programme. L'exiger dans une signature, c'est imposer à l'appelant de ne fournir que des données éternelles (littéraux, fuites mémoire, constantes). On ne lève pas la contrainte : on la durcit à l'extrême, et on la repousse sur tous ceux qui appellent.'static isn't a magic word for 'silence the error': it's the longest possible duration — the data lives until the program ends. Demanding it in a signature forces the caller to supply only eternal data (literals, leaks, constants). You don't lift the constraint: you harden it to the extreme, and push it onto everyone who calls you.

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

« Mes annotations deviennent-elles ingérables ? » Si oui, arrête d'annoter. Une signature qui croule sous les 'a: 'b n'est pas un problème de syntaxe : c'est un diagnostic. Elle dit qu'une structure tente de tenir des références qu'elle devrait posséder. Le remède est en amont, dans le design.'Are my annotations becoming unmanageable?' If so, stop annotating. A signature buckling under 'a: 'b isn't a syntax problem: it's a diagnosis. It says a structure is trying to hold references it ought to own. The cure is upstream, in the design.

'static a deux visages.'static has two faces.

Comme borne de durée sur une référence (&'static str), 'static dit « cette référence est valide pour toujours ». Comme borne sur un type générique (T: 'static), il dit « ce type ne contient aucune référence empruntée à durée courte » — un sens subtilement différent qu'on recroisera au Vol 7 avec les threads. Ici, c'est le premier sens, le piège, qui nous occupe.As a lifetime bound on a reference (&'static str), 'static says 'this reference is valid forever'. As a bound on a generic type (T: 'static), it says 'this type holds no short-lived borrowed reference' — a subtly different meaning we'll meet again in Vol 7 with threads. Here it's the first meaning, the trap, that concerns us.

№ 02La surpriseThe surprise4 min

Une référence dans une struct contamine tout ce qui la touche.A reference in a struct infects everything that touches it.

La spirale du numéro a une source presque unique : une struct qui stocke une référence. Dès qu'un champ est emprunté, la struct porte une durée — et cette durée se propage à chaque impl, chaque fonction, chaque struct qui la contient, comme une teinture qui remonte le tissu. C'est là que naissent les 'a en cascade. La question n'est pas « comment annoter cette struct », mais « cette struct doit-elle vraiment emprunter ? ».The issue's spiral has an almost single source: a struct storing a reference. The moment a field is borrowed, the struct carries a duration — and that duration propagates to every impl, every function, every struct containing it, like dye creeping up cloth. That's where cascading 'a are born. The question isn't 'how do I annotate this struct', but 'should this struct borrow at all?'.

Un champ &str, et la durée remonte partoutOne &str field, and the duration creeps everywhere
struct Config<'a> {           // la struct EMPRUNTE son nom
    nom: &'a str,
}

// la durée 'a contamine tout ce qui touche Config :
impl<'a> Config<'a> {
    fn nom(&self) -> &str { self.nom }
}
fn charger<'a>(src: &'a str) -> Config<'a> { /* ... */ }
struct App<'a> { config: Config<'a> }      // ...et ça remonte
// chaque appelant doit prouver que `src` survit à la Config. Partout.

Chaque <'a> de plus n'est pas une faute — c'est la conséquence mécanique du premier choix : le champ nom: &'a str. La struct ne possède pas son nom, donc elle doit prouver, à vie, que la donnée empruntée lui survit. Cette preuve, l'appelant doit la fournir partout — et la moindre construction qui ne peut pas la garantir échoue. C'est exactement le terrain où l'on dégaine 'static par dépit.Each extra <'a> isn't a mistake — it's the mechanical consequence of the first choice: the field nom: &'a str. The struct doesn't own its name, so it must prove, for life, that the borrowed data outlives it. That proof, the caller must supply everywhere — and any construction that can't guarantee it fails. This is exactly the ground where you reach for 'static out of spite.

Une struct qui emprunte est une struct qui dépend. Une struct qui possède ne doit rien à personne — et ne porte aucune durée.A struct that borrows is a struct that depends. A struct that owns owes nothing to anyone — and carries no duration.
№ 03La mécaniqueThe mechanics5 min

Trois manières de tenir une donnée — et une seule par défaut.Three ways to hold data — and only one default.

Devant un champ, une fonction, un type, le choix n'est pas « combien de 'a faut-il ». C'est, d'abord : faut-il posséder, emprunter, ou exiger l'éternel ? Les trois sont légitimes — mais pas interchangeables, et leur ordre de préférence est clair. On possède par défaut. On emprunte quand la vue est brève et locale. On exige 'static seulement quand la donnée est réellement éternelle — jamais pour faire taire une erreur.Facing a field, a function, a type, the question isn't 'how many 'a do I need'. It's, first: should I own, borrow, or demand the eternal? All three are legitimate — but not interchangeable, and their order of preference is clear. Own by default. Borrow when the view is brief and local. Demand 'static only when the data is genuinely eternal — never to silence an error.

PosséderOwnStringpar défautdefault
EmprunterBorrow&'a strvue brèvebrief view
ÉternelEternal&'static strrare, justifiérare, justified
← souple, on possèdeflexible, you owncontraignant, on exige →constraining, you demand →
PosséderOwn
String / Vec<T>
Le cas par défaut. La struct vit seule, se stocke, se déplace, se renvoie. Coût : une allocation.The default. The struct lives alone, stores, moves, returns. Cost: one allocation.
Emprunter (durée courte)Borrow (short-lived)
&'a str / &'a [T]
Vue éphémère, créée et consommée dans une même portée — un parseur, un itérateur. Jamais stockée longtemps.An ephemeral view, made and consumed in one scope — a parser, an iterator. Never stored long.
ÉternelEternal
&'static str
La donnée existe vraiment pour toute la durée du programme : littéraux, constantes. Pas un cache-misère.The data genuinely exists for the whole program: literals, constants. Not a band-aid.

L'ordre est aussi celui de préférence : possède, sauf raison de ne pas le faire.The order is also the preference: own, unless there's a reason not to.

Le 'static légitime, et le 'static-cache-misèreLegitimate 'static, and band-aid 'static

Il y a un usage parfaitement sain de 'static : une donnée qui est réellement éternelle. Un littéral &str est gravé dans le binaire — sa durée est 'static par nature, et l'écrire est honnête.There's a perfectly healthy use of 'static: data that genuinely is eternal. A &str literal is baked into the binary — its duration is 'static by nature, and writing it is honest.

const VERSION: &'static str = "1.0.0";   // honnête : vraiment éternel
fn defaut() -> &'static str { "anonyme" }   // un littéral : 'static naturel

// vs. le cache-misère, à fuir :
fn nom(c: &Config) -> &'static str {
    Box::leak(c.nom.clone().into_boxed_str())   // ✕ on FUIT de la mémoire
}                                                // pour fabriquer du 'static

Le dernier cas est le symptôme ultime de la spirale : ne pouvant prouver la durée, on fabrique de l'éternel en fuyant volontairement de la mémoire (Box::leak). Le compilateur est satisfait, le programme « marche » — et fuit un peu plus à chaque appel. On a transformé un problème de design en fuite mémoire silencieuse. Le bon geste était, trois étapes plus tôt : posséder.The last case is the spiral's ultimate symptom: unable to prove the duration, you manufacture the eternal by deliberately leaking memory (Box::leak). The compiler is happy, the program 'works' — and leaks a little more on every call. You've turned a design problem into a silent memory leak. The right move was, three steps earlier: own.

'static décrit une donnée qui vit pour toujours. Si la tienne ne vit pas pour toujours, l'écrire est un mensonge que la mémoire paiera.'static describes data that lives forever. If yours doesn't live forever, writing it is a lie that memory will pay for.
№ 04Le piègeThe trap4 min

Le mur final : la structure dont les nœuds se pointent mutuellement.The final wall: the structure whose nodes point at each other.

Il existe une classe de problèmes où aucune annotation, même 'static, ne peut aboutir : les structures à références croisées — un arbre dont les nœuds pointent vers leur parent et leurs enfants, un graphe avec des cycles, une liste doublement chaînée. Le débutant y voit la preuve que « Rust est trop rigide » et dégaine Rc<RefCell>, unsafe, ou des pointeurs bruts. Le bon réflexe est tout autre : cesser de stocker des références, et stocker des indices.There's a class of problems where no annotation, not even 'static, can succeed: cross-referencing structures — a tree whose nodes point to their parent and their children, a graph with cycles, a doubly linked list. The beginner takes this as proof that 'Rust is too rigid' and reaches for Rc<RefCell>, unsafe, or raw pointers. The right reflex is wholly different: stop storing references, and store indices.

Le contre-exemple — des références dans une structure cycliqueThe counter-example — references in a cyclic structure
// Un nœud qui connaît son parent ET ses enfants : des références dans les deux sens
struct Noeud<'a> {
    valeur: i32,
    parent: Option<&'a Noeud<'a>>,   // vers le haut…
    enfants: Vec<&'a Noeud<'a>>,     // … et vers le bas
}
let mut racine = Noeud { valeur: 0, parent: None, enfants: vec![] };
let fils = Noeud { valeur: 1, parent: Some(&racine), enfants: vec![] };
racine.enfants.push(&fils);   // ✕ error[E0502]: cannot borrow `racine.enfants` as mutable
                              //   because it is also borrowed as immutable
// Le parent doit exister AVANT l'enfant (pour être pointé),
// et être modifié APRÈS (pour pointer l'enfant) : aucun ordre ne marche.

Une référence promet que sa donnée lui survit. Dans un cycle, c'est impossible à prouver : A dépend de B qui dépend de A. Aucune durée ne satisfait les deux contraintes à la fois, et le compilateur a raison de refuser — la structure, telle qu'écrite, ne peut être libérée sans ambiguïté. Empiler les 'a ou tenter 'static ne fait que reculer le mur d'un pas.A reference promises its data outlives it. In a cycle, that's impossible to prove: A depends on B which depends on A. No duration satisfies both constraints at once, and the compiler is right to refuse — the structure, as written, can't be freed unambiguously. Stacking 'a or trying 'static only pushes the wall back one step.

Le bon réflexe — l'arène : posséder à plat, lier par indicesThe right reflex — the arena: own flat, link by indices
// On ne stocke pas des références, mais des INDICES dans une arène
struct Arbre {
    noeuds: Vec<Noeud>,              // tout est possédé, à plat
}
struct Noeud {
    valeur: i32,
    parent: Option<usize>,           // un index, pas une référence
    enfants: Vec<usize>,             // idem vers le bas
}
// Plus aucune durée : l'arène possède tout, les liens sont des entiers.
// On navigue par arbre.noeuds[i] — vérifié, sûr, sans un seul 'a.

Un Vec possède tous les nœuds, à plat ; les liens deviennent des usize — de simples entiers, sans durée. Le cycle disparaît parce qu'il n'y a plus qu'un seul propriétaire, l'arène, et que les indices ne prétendent rien sur la durée de vie. C'est le pattern qu'utilisent les vrais arbres et graphes en Rust — pas par contournement, mais parce que c'est le modèle de possession honnête pour une donnée cyclique.A Vec owns all the nodes, flat; the links become usize — plain integers, no duration. The cycle vanishes because there's now a single owner, the arena, and indices claim nothing about lifetime. This is the pattern real trees and graphs use in Rust — not as a workaround, but because it's the honest ownership model for cyclic data.

Les trois sorties, dans l'ordreThe three exits, in order

1. Possède la donnée (String, Vec) au lieu de l'emprunter. 2. Pour les graphes, remplace les références par des indices dans une arène. 3. En dernier recours seulement, le partage compté (Rc/Arc) — qu'on verra au Vol 6. Jamais 'static par dépit, ni unsafe par impatience.1. Own the data (String, Vec) instead of borrowing it. 2. For graphs, replace references with indices into an arena. 3. Only as a last resort, reference-counted sharing (Rc/Arc) — seen in Vol 6. Never 'static out of spite, nor unsafe out of impatience.

Le test mentalThe mental test

Quand les lifetimes deviennent un combat : « Et si je possédais cette donnée au lieu de la pointer ? » Neuf fois sur dix, la réponse dissout le problème. La dixième — vraie cyclicité — appelle l'arène et les indices. Le borrow checker ne t'interdit pas ta structure : il t'interdit de la tenir par des références qui mentent sur leur durée.When lifetimes turn into a fight: 'What if I owned this data instead of pointing to it?' Nine times in ten, the answer dissolves the problem. The tenth — true cyclicity — calls for the arena and indices. The borrow checker doesn't forbid your structure: it forbids holding it by references that lie about their duration.

Le borrow checker ne refuse pas ta structure. Il refuse que tu la tiennes par des références qui mentent sur leur durée.The borrow checker doesn't refuse your structure. It refuses you holding it by references that lie about their duration.
№ 05Bilan & éditoWrap-up & editorial2 min

Les lifetimes ne sont pas un obstacle. Ce sont un diagnostic.Lifetimes aren't an obstacle. They're a diagnosis.

Le Vol 2 s'achève là où il avait commencé : une référence ne doit jamais survivre à sa donnée. On a appris à relier les durées (№1), à laisser le compilateur les deviner (№2), et maintenant à reconnaître le moment où annoter ne sert plus à rien (№3). La leçon finale renverse la peur : quand les lifetimes se compliquent, ce n'est pas Rust qui résiste — c'est ton design de possession qui te parle.Vol 2 ends where it began: a reference must never outlive its data. We learned to link durations (№1), to let the compiler infer them (№2), and now to recognize the moment when annotating stops helping (№3). The final lesson flips the fear: when lifetimes get complicated, it isn't Rust resisting — it's your ownership design talking to you.

Le symptômeThe symptomCe qu'il révèleWhat it revealsLe bon gesteThe right move
'a en cascadeCascading 'aUne struct stocke une référence et contamine tout ce qui la touche.A struct stores a reference and infects everything around it.Possède le champ (String/Vec) au lieu de l'emprunter.Own the field (String/Vec) instead of borrowing it.
&'static par dépitout of spiteOn exige l'éternel pour faire taire une erreur de durée.Demanding the eternal to silence a duration error.N'utilise 'static que pour une donnée vraiment éternelle (littéraux).Use 'static only for genuinely eternal data (literals).
Box::leakFabriquer du 'static en fuyant de la mémoire : design caché sous un leak.Manufacturing 'static by leaking memory: design hidden under a leak.Recule de trois pas : la donnée devait être possédée.Back up three steps: the data should have been owned.
Auto-référenceSelf-referenceArbre, graphe, cycle : aucune durée ne peut satisfaire un cycle.Tree, graph, cycle: no duration can satisfy a cycle.Arène : possède à plat dans un Vec, lie par indices (usize).Arena: own flat in a Vec, link by indices (usize).
Quand annoter ne suffit plus, arrête d'annoter. La signature qui croule te montre où la possession est mal placée.When annotating stops helping, stop annotating. The buckling signature shows you where ownership sits wrong.
Mot de l'éditeurFrom the editor

Deux volumes pour apprendre à ne pas mentir au compilateur.Two volumes to learn not to lie to the compiler.

Le Vol 1 et le Vol 2 forment un seul arc. La possession répondait à « qui détient cette donnée » ; les lifetimes, à « combien de temps une vue peut-elle la regarder ». Mais les deux, au fond, demandent la même discipline : dire au compilateur la vérité sur la mémoire — qui possède, qui emprunte, et pour combien de temps. Toutes les batailles qu'on a perdues venaient d'une tentative de lui faire croire autre chose : qu'une vue survivrait à sa donnée, qu'une struct n'aurait pas à choisir entre posséder et emprunter, qu'un 'static réglerait ce qu'un bon design aurait évité.Vol 1 and Vol 2 form a single arc. Ownership answered 'who holds this data'; lifetimes, 'how long may a view look at it'. But both, at heart, demand the same discipline: tell the compiler the truth about memory — who owns, who borrows, and for how long. Every battle we lost came from trying to make it believe otherwise: that a view would outlive its data, that a struct needn't choose between owning and borrowing, that a 'static would fix what good design would have avoided.

Volume 3 : le type qui rend l'illégal impossible. On quitte la mémoire pour les types. Jusqu'ici, le compilateur nous empêchait de mal manier la donnée ; on va voir comment lui faire interdire des états entiers — rendre les mauvaises valeurs non pas détectables, mais inexprimables. Le même principe que « aliasing XOR mutation », élevé au rang de méthode de conception. On arrête de vérifier les erreurs : on les rend impossibles à écrire.Volume 3: the type that makes the illegal impossible. We leave memory for types. So far, the compiler kept us from mishandling data; we'll see how to make it forbid whole states — rendering bad values not detectable, but inexpressible. The same principle as 'aliasing XOR mutation', raised to a design method. We stop checking for errors: we make them impossible to write.