ProgrammationDéveloppeur Rust

Comment les opérations sur les tranches (slice) sont-elles mises en œuvre en Rust et quelles sont leurs différences par rapport aux tableaux et vecteurs en ce qui concerne la gestion de la mémoire et la sécurité ?

Réussissez les entretiens avec l'assistant IA Hintsage

Réponse.

Historique de la question

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.

Problème

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.

Solution

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 :

  • La slice ne possède pas de données et est toujours active pas plus longtemps que la source des données
  • En cas de dépassement, panic ou erreur de compilation, le traitement est sécurisé
  • Prise en charge des slices immuables et mutables (immuable - lecture seule, mutable - permet de modifier les données dans la source)

Questions pièges.

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

Erreurs typiques et anti-patrons

  • Tenter de retourner une slice sur un tableau local depuis une fonction entraîne des erreurs de durée de vie.
  • Mélanger la possession des slices et des collections originales (double allocation, accès incorrect lors de la recréation de la collection).

Exemple de la vie réelle

Cas négatif

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 :

  • Simple, si l'on ne pense pas aux durées de vie

Inconvénients :

  • Possibilité d'UB
  • Ne passe pas la compilation en Rust

Cas positif

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 :

  • Garantie de sécurité
  • Pas de pointeurs "dangling"
  • Possibilité de fragmenter facilement de grands tableaux en parties sûres

Inconvénients :

  • Une architecture de durée de vie des données doit être réfléchie