Les tranches (slice, types [T] et &[T]) ont été introduites dans Rust pour un accès sûr et efficace à des sous-ensembles de tableaux, vecteurs et autres séquences d'éléments. Elles permettent d'éviter des allocations et des copies répétées de données, en fournissant uniquement une "vue" ou une fenêtre sur une partie de la collection. Cela diffère à la fois des tableaux, qui ont une taille fixe au moment de la compilation, et des collections dynamiques, qui stockent un pointeur et une longueur, mais possèdent la mémoire.
Lors de la manipulation de tableaux et de vecteurs dans des langages sans contrôle strict de la durée de vie, il y a souvent des erreurs de dépassement de capacité (out of bounds), des fuites de mémoire et l'utilisation de pointeurs incorrects. Il est important de s'assurer que l'on ne copie pas et que la sécurité de la mémoire est respectée lors de la manipulation de sous-ensembles de collections, ce qui est particulièrement pertinent au niveau système.
En Rust, une slice est un "pointeur + longueur" vers une partie des données, ne possédant pas le contenu. Elles sont toujours accompagnées d'une durée de vie (lifetime), et le compilateur garantit qu'une slice ne survit pas à l'original (array, Vec, String). Tout travail avec les slices se fait via des méthodes d'accès sécurisées, et tout dépassement de bord entraîne un panic à l'exécution.
Exemple de code :
let arr = [1, 2, 3, 4, 5]; let slice = &arr[1..4]; // [2,3,4] type: &[i32] let mut vec = vec![10, 20, 30]; let mut_slice: &mut [i32] = &mut vec[..2]; mut_slice[0] = 99; assert_eq!(vec, [99, 20, 30]);
Caractéristiques clés :
Peut-on créer une slice dépassant la taille du tableau ou du vecteur d'origine ?
Non. Le compilateur et l'exécution garantissent qu'une slice ne peut être créée que dans des indices valides des données d'origine. Tenter de dépasser les limites entraînera un panic.
let arr = [1, 2, 3]; let s = &arr[0..4]; // panic à l'exécution
Les slices sont-elles des propriétaires autonomes de la mémoire ?
Non. Les slices ne sont qu'une "fenêtre" sur les données, elles ne possèdent pas la mémoire. Tenter de retourner une slice d'une fonction si la source est locale entraînera une erreur de compilation.
fn give_slice() -> &[i32] { let arr = [1,2,3]; &arr[1..] } // erreur : arr ne vit pas assez longtemps
Quelles sont les différences entre les slices et les tableaux en Rust au niveau des types et des opérations ?
Le tableau a une longueur fixe, connue au moment de la compilation, et est entièrement imbriqué dans la pile. La slice peut avoir n'importe quelle longueur, définie dynamiquement, et stocke toujours un pointeur et une longueur.
let a: [u32; 3] = [1,2,3]; // Tableau de longueur fixe let s: &[u32] = &a[..]; // Slice de n'importe quelle taille
Un programmeur a retourné une slice d'une fonction, où un tableau local avait été créé. Après la sortie, la fonction a supprimé l'original, et la slice est devenue un pointeur "dangling". Cela a causé un bug et même un plantage.
Avantages :
Inconvénients :
La slice est toujours créée par référence à des données externes, le propriétaire des données et la slice vivent aussi longtemps l'un que l'autre. Le compilateur garantit un lien étroit de durée de vie entre la slice et la source.
Avantages :
Inconvénients :