Les slices en Rust
Une slice (tranche) vous permet d’emprunter une partie d’un tableau ou d’un vecteur, une sorte de « fenêtre » sur une portion des données, sans copier ces données ni en prendre l’ownership (possession). Si vous avez déjà utilisé le borrowing (emprunt) avec une seule valeur, une slice, c’est la même idée appliquée à une plage d’éléments.
Prendre une slice d’un tableau
Section intitulée « Prendre une slice d’un tableau »fn main() { let numbers = [10, 20, 30, 40, 50];
let middle = &numbers[1..4]; // les éléments aux indices 1, 2 et 3 println!("{:?}", middle);}Sortie :
[20, 30, 40]1..4 est le même genre de plage que celle des boucles for : elle commence à l’indice 1 et s’arrête avant l’indice 4. Vous obtenez donc les indices 1, 2 et 3.
Les raccourcis pour les plages
Section intitulée « Les raccourcis pour les plages »Quelques raccourcis que vous croiserez souvent :
fn main() { let numbers = [10, 20, 30, 40, 50];
println!("{:?}", &numbers[..3]); // du début jusqu'à l'indice 3 -> [10, 20, 30] println!("{:?}", &numbers[2..]); // de l'indice 2 jusqu'à la fin -> [30, 40, 50] println!("{:?}", &numbers[..]); // le tout -> [10, 20, 30, 40, 50]}Les slices de chaînes de caractères
Section intitulée « Les slices de chaînes de caractères »En fait, vous utilisez déjà des slices : &str est une slice d’une String (ou d’une autre chaîne) ! C’est pour ça que les deux types fonctionnent si bien ensemble :
fn main() { let sentence = String::from("The quick brown fox");
let first_word = &sentence[0..3]; println!("{first_word}"); // "The"}La phrase reste en anglais exprès : les indices d’une slice de texte comptent des octets, et une lettre accentuée comme « é » en occupe deux. Avec une phrase en français, 0..3 ne tomberait pas forcément sur la fin d’un mot.
Pourquoi des slices plutôt que des copies ?
Section intitulée « Pourquoi des slices plutôt que des copies ? »On pourrait imaginer résoudre le problème en copiant la portion voulue dans un nouveau Vec ou une nouvelle String. Les slices évitent complètement cela : elles pointent simplement vers une partie des données existantes, de la même façon qu’une référence (&) pointe vers une seule valeur. Résultat : aucune allocation en plus, aucune copie, et les règles d’emprunt que vous connaissez déjà grâce à la page Borrowing s’appliquent toujours.
Une fonction qui reçoit une slice
Section intitulée « Une fonction qui reçoit une slice »Les slices sont particulièrement utiles comme paramètres de fonction, car elles acceptent à la fois les tableaux et les vecteurs :
fn sum(numbers: &[i32]) -> i32 { let mut total = 0; for n in numbers { total += n; } total}
fn main() { let array = [1, 2, 3]; let vector = vec![4, 5, 6];
println!("{}", sum(&array)); println!("{}", sum(&vector));}&[i32] accepte une slice venant aussi bien d’un tableau que d’un Vec : une seule fonction, et les deux appels fonctionnent.
Modifier des données avec une slice mutable
Section intitulée « Modifier des données avec une slice mutable »Jusqu’ici, nos slices se contentaient de lire les données. Une slice mutable, &mut [T], permet à une fonction de modifier les éléments qu’on lui a donnés, et seulement ceux-là :
fn double_all(numbers: &mut [i32]) { for i in 0..numbers.len() { numbers[i] *= 2; }}
fn main() { let mut scores = vec![1, 2, 3, 4, 5];
double_all(&mut scores[1..3]); // seulement les indices 1 et 2 println!("{:?}", scores);
double_all(&mut scores); // le vecteur entier println!("{:?}", scores);}Sortie :
[1, 4, 6, 4, 5][2, 8, 12, 8, 10]Le premier appel passe &mut scores[1..3], donc double_all ne voit que les indices 1 et 2. Tout ce qui se trouve en dehors de cette plage est hors de sa portée. À l’intérieur de la fonction, la slice recommence à l’indice 0 : pour double_all, c’est simplement une liste de deux nombres.
C’est sûr grâce à une règle que vous verrez en détail sur la page Borrowing : tant que la fonction détient une slice &mut, rien d’autre ne peut utiliser ces éléments. Deux parties de votre code ne peuvent donc jamais les modifier en même temps. Le vecteur doit aussi être déclaré mut, sinon Rust refuse de l’emprunter de façon mutable.
Exercices
Section intitulée « Exercices »1. Couper en deux
Section intitulée « 1. Couper en deux »Créez le vecteur vec![3, 6, 9, 12, 15, 18]. Affichez sa première moitié et sa seconde moitié sous forme de deux slices séparées. Indice : calculez let mid = numbers.len() / 2; et utilisez ..mid et mid...
Voir la solution
fn main() { let numbers = vec![3, 6, 9, 12, 15, 18]; let mid = numbers.len() / 2;
let first_half = &numbers[..mid]; let second_half = &numbers[mid..];
println!("Première moitié : {:?}", first_half); println!("Seconde moitié : {:?}", second_half);}Sortie :
Première moitié : [3, 6, 9]Seconde moitié : [12, 15, 18]Comme mid est calculé à partir de len(), ce code continue de fonctionner si vous ajoutez ou retirez des nombres. Avec une longueur impaire, l’élément en trop se retrouve dans la seconde moitié.
2. Le plus grand de n’importe quelle liste
Section intitulée « 2. Le plus grand de n’importe quelle liste »Écrivez une fonction largest(numbers: &[i32]) -> i32 qui renvoie le plus grand nombre. Appelez-la avec un tableau, avec un vecteur, et avec une slice qui ne contient que le premier élément du vecteur. Indice : partez de numbers[0] et parcourez le reste avec une boucle, comme vous le feriez avec un tableau.
Voir la solution
fn largest(numbers: &[i32]) -> i32 { let mut biggest = numbers[0]; for n in numbers { if *n > biggest { biggest = *n; } } biggest}
fn main() { let array = [7, 2, 9, 4]; let vector = vec![10, 35, 21];
println!("{}", largest(&array)); println!("{}", largest(&vector)); println!("{}", largest(&vector[..1]));}Sortie :
93510Parcourir une slice avec une boucle vous donne une référence vers chaque élément (&i32), pas le nombre lui-même. *n signifie « la valeur vers laquelle pointe n », donc *n > biggest compare deux nombres ordinaires. Cette version provoque un panic sur une slice vide, car numbers[0] n’existe pas. Plus tard, vous apprendrez à renvoyer une Option dans ce cas.
Récapitulatif
Section intitulée « Récapitulatif »- Une slice (
&[T]) emprunte une partie d’un tableau ou d’un vecteur sans la copier. 1..4sélectionne les indices 1 à 3 ; omettez un côté de..pour partir du début ou aller jusqu’à la fin.&strest lui-même une slice de texte : c’est pour ça que les chaînes et les slices se comportent de façon si proche.- Préférez
&[T]comme type de paramètre quand vous voulez accepter à la fois des tableaux et des vecteurs. - Une slice mutable (
&mut [T]) permet à une fonction de modifier uniquement les éléments qu’on lui a donnés.
Petit quiz
Section intitulée « Petit quiz »Petit quiz
1. Que permet une slice avec une partie d'un tableau ou d'un vecteur ?
2. Quels indices &numbers[1..4] inclut-il ?
3. Pourquoi &[i32] est-il un bon choix comme type de paramètre de fonction ?
Score : 0 / 3
Pour aller plus loin
Section intitulée « Pour aller plus loin »- The Slice Type — The Rust Book (en anglais) : introduit les slices pas à pas avec un exemple « premier mot d’une chaîne ».
- Arrays and Slices — Rust by Example (en anglais) : des exemples exécutables où l’on emprunte des tableaux sous forme de slices.
slice— documentation de la bibliothèque standard (en anglais) : les nombreuses méthodes des slices, commefirst,split_at,containsetsort.- Slice types — The Rust Reference (en anglais) : la définition précise, si vous voulez savoir exactement ce que signifient
[T]et&[T].