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

Slices, ou regarder sans copier ni posséder Slices, or looking without copying or owning

&str, &[T] : une fenêtre sur une donnée, sans allocation ni possession. Le réflexe qui désamorce 80 % des combats futurs : prends &str en paramètre là où tu prenais String. &str, &[T]: a window onto data, with no allocation and no ownership. The reflex that defuses 80% of future fights: take &str where you were taking String.

AudienceAudience
Dev venant d'un langage à GC, ayant vu move et borrowing Dev from a GC language, who has seen move and borrowing
Format
Self-paced
ChapitresChapters
5
Date
Juin 2026 Jun 2026
≈ 18 min ●○○○ PossessionSlicesstr
SommaireContents ·
№ 01CadrageFraming3 min

Une fenêtre sur une donnée. Ni copie, ni possession.A window onto data. Neither copy nor ownership.

Le move déplace, l'emprunt prête la donnée entière. La slice est le troisième geste : emprunter une fenêtre — une portion contiguë — d'une collection, sans la copier ni la posséder. &str est une vue dans un String ; &[T] une vue dans un Vec. Ce n'est qu'un pointeur et une longueur : regarder une sous-partie ne coûte rien.The move relocates, the borrow lends the whole value. The slice is the third gesture: borrowing a window — a contiguous portion — of a collection, without copying or owning it. &str is a view into a String; &[T] a view into a Vec. It's just a pointer and a length: looking at a sub-part costs nothing.

Deux vues, une seule donnéeTwo views, one piece of data
let phrase = String::from("hello world");

let hello = &phrase[0..5];   // une VUE sur les 5 premiers octets
let world = &phrase[6..11];  // une autre vue, même donnée sous-jacente

println!("{hello} / {world}");   // hello / world — zéro copie, zéro alloc
Une slice = pointeur + longueurA slice = pointer + length
h
0
e
1
l
2
l
3
o
4
␣
5
w
6
o
7
r
8
l
9
d
10
&phrase[0..5] — ptr → 0, len 5

La slice ne contient pas les caractères : elle pointe à l'intérieur du String, avec une longueur. Le buffer reste possédé par phrase — la vue n'en est qu'un emprunt cadré.The slice doesn't hold the characters: it points inside the String, with a length. The buffer stays owned by phrase — the view is just a framed borrow of it.

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

« Ai-je besoin de posséder cette donnée, ou seulement d'en regarder une partie ? » La réponse, presque toujours, est : regarder. Et alors la slice — &str, &[T] — est le bon type, pas le String ni le Vec possédés.'Do I need to own this data, or only to look at part of it?' The answer, almost always, is: look. And then the slice — &str, &[T] — is the right type, not the owned String or Vec.

Pourquoi ce numéro clôt le Vol 1.Why this issue closes Vol 1.

Move, emprunt, slice : la trilogie de la possession. On sait déplacer une valeur, la prêter en entier, et maintenant en prêter une fenêtre. C'est le réflexe qui, à lui seul, désamorce la plupart des combats futurs contre le borrow checker — parce qu'on arrête de posséder ce qu'on voulait juste lire.Move, borrow, slice: the ownership trilogy. We can relocate a value, lend it whole, and now lend a window onto it. It's the reflex that, on its own, defuses most future fights with the borrow checker — because you stop owning what you only meant to read.

№ 02Le couplageThe coupling4 min

Demander un String quand on voulait juste lire.Asking for a String when you only wanted to read.

Venant d'un langage à GC, passer une chaîne « ne coûte rien » : tout est référence implicite. En Rust, écrire fn salue(nom: String) dit littéralement « donne-moi la possession de ton String ». L'appelant doit céder le sien — ou en fabriquer un par clone. Une signature trop gourmande, et tout le code autour paie.Coming from a GC language, passing a string 'costs nothing': everything is an implicit reference. In Rust, writing fn salue(nom: String) literally says 'hand me ownership of your String'. The caller must give theirs up — or fabricate one by cloning. One greedy signature, and all the surrounding code pays.

String en paramètre — l'appelant doit céderString parameter — the caller must give up
fn salue(nom: String) {        // exige de POSSÉDER un String
    println!("Salut {nom}");
}

salue(String::from("Ada"));    // ok
salue("Bob");                  // ✕ "Bob" est &str, pas String
let n = String::from("Cléo");
salue(n);                      // ok, mais n est MOVED — on ne l'a plus
salue(n);                      // ✕ error[E0382]: n a déjà été donné

La fonction ne fait que lire nom — mais sa signature exige la possession. Conséquence : un littéral ne passe pas, et toute variable passée est consommée. Pour la réutiliser, on en vient à cloner « pour que ça passe » — le piège des numéros 1 et 2, importé par une seule mauvaise signature.The function only reads nom — yet its signature demands ownership. The fallout: a literal won't pass, and any variable passed is consumed. To reuse it, you end up cloning 'to make it pass' — the trap from issues 1 and 2, imported by one bad signature.

Une signature qui prend un String dit « donne-moi tout ». Une qui prend un &str dit « montre-moi, je regarde ».A signature taking a String says 'give me everything'. One taking a &str says 'show me, I'll look'.
№ 03La mécaniqueThe mechanics5 min

Un pointeur, une longueur — et la déréférence qui relie tout.A pointer, a length — and the deref that ties it together.

Sous le capot, une slice est un fat pointer : deux mots — l'adresse du premier élément, et le nombre d'éléments. Rien d'autre. C'est ce qui rend &str strictement plus général que &String : un &String peut toujours devenir un &str (par déréférence automatique), l'inverse n'est pas vrai. Accepter une vue, c'est accepter le plus de monde possible avec le moins d'exigence.Under the hood, a slice is a fat pointer: two words — the address of the first element, and the count of elements. Nothing else. That's what makes &str strictly more general than &String: a &String can always become a &str (by auto-deref), the reverse isn't true. Accepting a view means accepting the widest set of callers with the least demand.

Le fat pointer : deux mots, zéro donnéeThe fat pointer: two words, zero data

Ce que contient une sliceWhat a slice holds
pointeurpointer
0x7ffe…a0
vers le 1ᵉʳ octetto the 1st byte
longueurlength
5
nombre d'octetsbyte count

C'est tout : 16 octets sur une machine 64 bits, quelle que soit la taille de la donnée visée. Copier une slice ne copie pas les octets du texte — seulement ces deux mots. La donnée, elle, reste là où son propriétaire l'a posée.That's all: 16 bytes on a 64-bit machine, whatever the size of the data it points at. Copying a slice doesn't copy the text's bytes — only those two words. The data itself stays wherever its owner put it.

La déréférence : pourquoi &str avale &StringDeref coercion: why &str swallows &String

Quand une fonction attend &str et qu'on lui donne &String, Rust insère silencieusement la conversion : c'est la deref coercion. Idem pour &Vec<T> → &[T]. Conséquence pratique : un paramètre &str accepte le littéral, le &String, et toute sous-tranche — un seul type d'entrée, trois sources.When a function expects &str and you hand it a &String, Rust silently inserts the conversion: that's deref coercion. Same for &Vec<T> → &[T]. Practical upshot: a &str parameter accepts the literal, the &String, and any sub-slice — one input type, three sources.

fn premier_mot(s: &str) -> &str {
    s.split(' ').next().unwrap_or("")
}

let owned = String::from("hello world");

premier_mot(&owned);       // &String  → &str   (déréf automatique)
premier_mot("hello world");// &str             (littéral, déjà une vue)
premier_mot(&owned[6..]);  // &str             (une sous-tranche)

// &Vec<T> se déréférence de même en &[T] :
fn somme(v: &[i32]) -> i32 { v.iter().sum() }
let nums = vec![1, 2, 3];
somme(&nums);              // &Vec<i32> → &[i32]
somme(&nums[1..]);         // &[i32]
La hiérarchieThe hierarchy

&str est en bas de la pyramide des exigences : tout ce qui est du texte peut s'y réduire. &String exige déjà une allocation tas ; String exige en plus la possession. Demander &str, c'est demander le strict minimum — du texte à lire.&str sits at the bottom of the demand pyramid: anything textual reduces to it. &String already requires a heap allocation; String further requires ownership. Asking for &str is asking the bare minimum — text to read.

Une borne, pas un caractère.An index is a byte, not a char.

Les bornes d'une slice de String comptent des octets UTF-8, pas des caractères. Trancher au milieu d'un caractère multi-octets (un emoji, un « é ») panique à l'exécution. Pour du texte non-ASCII, on tranche sur des frontières connues — .char_indices(), .find(), jamais un index deviné. On y reviendra ; pour l'instant, retiens que &str[i..j] travaille en octets.String slice bounds count UTF-8 bytes, not characters. Slicing inside a multi-byte character (an emoji, an 'é') panics at runtime. For non-ASCII text you slice on known boundaries — .char_indices(), .find(), never a guessed index. More on that later; for now, remember &str[i..j] works in bytes.

Une slice ne possède rien et n'alloue rien : elle désigne. C'est le type le plus pauvre — et c'est exactement pourquoi c'est le plus utile.A slice owns nothing and allocates nothing: it designates. It's the poorest type — and that's exactly why it's the most useful.
№ 04Le piègeThe trap4 min

Prendre String « par habitude » là où &str suffisait.Taking String 'out of habit' where &str would do.

Le réflexe vient d'autres langages : un paramètre de type « chaîne », on écrit le type chaîne — String. Mais en Rust, String dit « je veux la possession ». Si la fonction ne fait que lire, cette exigence ne sert à rien : elle force l'appelant à céder sa donnée ou à cloner, refuse les littéraux, et couple toute l'API à une allocation tas dont elle n'avait pas besoin. Le coût n'est pas dans la fonction — il est chez tous ceux qui l'appellent.The reflex comes from other languages: a 'string' parameter, so you write the string type — String. But in Rust, String says 'I want ownership'. If the function only reads, that demand buys nothing: it forces the caller to give up their data or clone, refuses literals, and couples the whole API to a heap allocation it never needed. The cost isn't in the function — it's on everyone who calls it.

Le contre-exemple — String exigé pour rienThe counter-example — String demanded for nothing
// Une fonction qui ne fait que LIRE, mais exige de posséder
fn est_palindrome(mot: String) -> bool {
    let inverse: String = mot.chars().rev().collect();
    mot == inverse
}

let titre = String::from("kayak");
est_palindrome(titre.clone());  // clone « pour que ça passe »
est_palindrome(titre);          // ou move : on perd titre
// est_palindrome("ressasser") ne compile même pas : &str ≠ String

La fonction lit mot, compare, et c'est tout — elle n'a aucune raison de le posséder. Pourtant sa signature l'exige, et le coût se propage : l'appelant clone (une allocation entière pour un test booléen), ou cède sa variable et la perd, ou n'a tout simplement pas de String sous la main et reste bloqué. Une mauvaise signature taxe tout le code autour d'elle.The function reads mot, compares, and that's all — it has no reason to own it. Yet its signature demands it, and the cost spreads: the caller clones (a whole allocation for a boolean test), or surrenders their variable and loses it, or simply has no String on hand and is stuck. A bad signature taxes all the code around it.

Le bon réflexe — accepter une vueThe right reflex — accept a view
// La même logique, mais on emprunte une vue
fn est_palindrome(mot: &str) -> bool {
    let inverse: String = mot.chars().rev().collect();
    mot == inverse
}

let titre = String::from("kayak");
est_palindrome(&titre);       // &String → &str, titre intact
est_palindrome("ressasser");  // littéral, directement
est_palindrome(&titre[1..4]); // une sous-tranche — zéro alloc

Un seul caractère change — String devient &str — et tout se débloque : le &String est accepté par déréférence, le littéral passe directement, la sous-tranche aussi, et titre survit à l'appel. Zéro clone, zéro allocation, plus d'appelants servis. La fonction n'a rien perdu : elle ne faisait que lire, elle ne demande plus que de lire.One character changes — String becomes &str — and everything unlocks: the &String is accepted by deref, the literal passes directly, the sub-slice too, and titre survives the call. Zero clone, zero allocation, more callers served. The function lost nothing: it only ever read, now it only asks to read.

La règle, généraliséeThe rule, generalized

Pour les API : accepte &str et &[T] en entrée, retourne String et Vec<T> en sortie. On exige le minimum de l'appelant, on lui rend une donnée qu'il possède. C'est la convention idiomatique — emprunte ce que tu lis, possède ce que tu produis.For APIs: accept &str and &[T] as input, return String and Vec<T> as output. You demand the minimum from the caller, you hand back data they own. That's the idiomatic convention — borrow what you read, own what you produce.

L'exception qui confirme.The exception that confirms.

Prends String en paramètre quand la fonction a vraiment besoin de garder la donnée — la stocker dans une struct, la renvoyer transformée par move, la pousser dans un Vec qui lui survit. Là, exiger la possession est honnête : tu la demandes parce que tu vas vraiment la garder. Le piège n'est pas String — c'est String quand tu ne fais que lire.Take String as a parameter when the function genuinely needs to keep the data — store it in a struct, return it moved-and-transformed, push it into a Vec that outlives the call. There, demanding ownership is honest: you ask for it because you'll really keep it. The trap isn't String — it's String when you only read.

Demander String pour lire, c'est exiger les clés de la maison pour regarder par la fenêtre.Demanding String to read is asking for the keys to the house just to look through the window.
№ 05Bilan & éditoWrap-up & editorial2 min

Trois gestes, et la possession devient jouable.Three gestures, and ownership becomes playable.

Le Vol 1 tenait en une trilogie. Le move : une valeur, un propriétaire — déplacer, pas copier. L'emprunt : prêter la donnée entière, sous la règle aliasing XOR mutation. La slice : prêter une fenêtre, sans copier ni posséder. Ensemble, ils répondent à la seule question qui compte devant chaque type : ai-je besoin de posséder, ou seulement de regarder ?Vol 1 came down to a trilogy. The move: one value, one owner — relocate, don't copy. The borrow: lend the whole value, under the aliasing XOR mutation rule. The slice: lend a window, without copying or owning. Together they answer the only question that matters at every type: do I need to own, or only to look?

La vueThe viewCe qu'elle estWhat it isLe réflexeThe reflex
&strUne fenêtre sur du texte : pointeur + longueur. Vue dans un String ou un littéral.A window onto text: pointer + length. A view into a String or a literal.En paramètre, toujours préférable à String quand on ne fait que lire.As a parameter, always preferable to String when you only read.
&[T]Une fenêtre sur une séquence : vue dans un Vec ou un tableau.A window onto a sequence: a view into a Vec or an array.Même logique : accepte &[T], pas Vec<T>, pour lire une collection.Same logic: accept &[T], not Vec<T>, to read a collection.
Déréf.Deref&String → &str, &Vec<T> → &[T], insérés automatiquement par le compilateur.&String → &str, &Vec<T> → &[T], inserted automatically by the compiler.C'est ce qui rend &str strictement plus général que &String.It's what makes &str strictly more general than &String.
L'APIThe APIEmprunte ce que tu lis, possède ce que tu produis.Borrow what you read, own what you produce.&str / &[T] en entrée, String / Vec<T> en sortie.&str / &[T] in, String / Vec<T> out.
La plupart des combats contre le borrow checker se gagnent en amont, par une signature : emprunte une vue, ne prends pas possession.Most fights with the borrow checker are won upstream, in a signature: borrow a view, don't take ownership.
Mot de l'éditeurFrom the editor

La possession n'était que la moitié de la question.Ownership was only half the question.

Tout le Vol 1 a répondu à « qui possède cette donnée, et qui a le droit d'y toucher ». Mais une slice soulève aussitôt une seconde question, qu'on a soigneusement laissée de côté. Une vue pointe à l'intérieur d'une donnée qu'elle ne possède pas. Que se passe-t-il si le propriétaire libère cette donnée pendant que la vue la regarde encore ? Dans un langage à pointeurs nus, c'est le use-after-free — la vue désigne de la mémoire morte. Rust l'interdit, et ce numéro ne t'a jamais expliqué comment.All of Vol 1 answered 'who owns this data, and who may touch it'. But a slice immediately raises a second question we carefully set aside. A view points inside data it doesn't own. What happens if the owner frees that data while the view is still looking at it? In a raw-pointer language, that's use-after-free — the view designates dead memory. Rust forbids it, and this issue never told you how.

La réponse est le sujet du Vol 2 : les lifetimes. Le compilateur suit, pour chaque emprunt, combien de temps il peut vivre — et garantit qu'une vue ne survit jamais à la donnée qu'elle regarde. Le 'a qu'on voyait passer dans les signatures sans le comprendre, c'est ça : la durée. On a appris à emprunter une fenêtre ; on va apprendre à prouver qu'elle se referme à temps.The answer is the subject of Vol 2: lifetimes. For each borrow, the compiler tracks how long it may live — and guarantees a view never outlives the data it looks at. The 'a you've seen drifting through signatures without grasping it, that's it: duration. We've learned to borrow a window; we're about to learn to prove it closes in time.