ProgrammationDéveloppeur système

Quelles sont les approches pour travailler en toute sécurité avec les ressources dynamiques (heap) en Rust, et comment éviter les fuites de mémoire ou les pointeurs pendants lors de l'utilisation directe de pointeurs ?

Réussissez les entretiens avec l'assistant IA Hintsage

Réponse.

Historique de la question :

Rust a été conçu dès le départ comme un langage dont la priorité est la sécurité de la mémoire. Cependant, dans certaines tâches — par exemple, lors de l'interaction avec FFI ou les allocateurs de bas niveau — il est nécessaire d'utiliser des pointeurs bruts et de gérer la mémoire dynamique manuellement. Ces tâches se rencontrent aussi bien en programmation système que dans l'optimisation des performances. Il est donc important de savoir comment Rust prévient les fuites, les pointeurs pendants et les accès après libération.

Problème :

Les pointeurs bruts (*const T, *mut T) ne font pas partie du système de possession et de gestion des références de Rust : ils peuvent pointer vers une mémoire invalide, être libérés de manière incorrecte ou ne pas être libérés du tout. Une erreur dans leur manipulation peut entraîner un comportement indéfini (UB), des plantages, des vulnérabilités de sécurité ou des fuites de mémoire.

Solution :

Au lieu d'utiliser des pointeurs bruts, il est recommandé d'utiliser des types sûrs — Box, Rc, Arc, et pour les références temporaires — des références empruntées. Cependant, si l'utilisation de pointeurs bruts est inévitable (par exemple, pour travailler avec une API C), tout le travail doit être encapsulé dans des blocs unsafe, en organisant soigneusement le trait Drop, et en utilisant autant que possible des crates comme NonNull. Une autre technique consiste à utiliser des wrappers RAII et à minimiser le cycle de vie d'un pointeur.

Exemple de code :

fn allocate_in_heap() -> Box<i32> { Box::new(100) } // la mémoire sera libérée automatiquement // avec un pointeur brut unsafe fn leak_memory() { let ptr = libc::malloc(4) as *mut i32; if !ptr.is_null() { *ptr = 42; // libc::free(ptr); // si on oublie de libérer — fuite ! } }

Caractéristiques clés :

  • Les types sûrs (Box, Rc, Arc) respectent les règles de possession de mémoire
  • Les pointeurs bruts unsafe ne sont autorisés que dans des scénarios spécifiques et nécessitent une libération manuelle
  • Les traits Drop et l'approche RAII protègent contre la majorité des fuites

Questions pièges.

Le Box garantit-il le nettoyage automatique de toutes les valeurs imbriquées lors de la suppression de Box ?

Oui, lors de la suppression de Box<T>, le destructeur appelle d'abord le nettoyage de l'enveloppe elle-même, puis de manière récursive — de toutes les données imbriquées à l'intérieur (jusqu'aux éléments de Vec ou d'autres Box à l'intérieur de la structure T).

Peut-on transmettre en toute sécurité un pointeur brut d'une structure à travers plusieurs fonctions, sans risque d'accéder à une mémoire libérée ?

Non, le pointeur brut ne contient pas d'information sur la durée de vie de l'objet. Le compilateur ne peut pas vérifier la sécurité, donc la responsabilité incombe entièrement au développeur : si l'objet est libéré, le pointeur brut pointera vers le vide.

Si on utilise manuellement free ou drop_in_place, Rust peut-il appeler Drop deux fois pour la même adresse ?

Oui, si après une libération manuelle un autre Box/pointeur faisant référence à ce même bloc est laissé, alors lors de la destruction du second exemplaire, Drop sera appelé à nouveau, entraînant un comportement indéfini (UB). Il ne faut jamais libérer manuellement ce qui est géré par Box, Vec, etc.

Erreurs typiques et anti-patrons

  • Violation de la possession : libération d'une ressource manuellement + par le destructeur automatique (double free)
  • Fuites de mémoire via mem::forget ou pointeur brut non libéré
  • Transmission d'un pointeur au-delà de la durée de vie du contenu
  • Mémoire non initialisée (alloca sans write)

Exemple de la vie réelle

Cas négatif

Un programmeur a pris un pointeur brut d'une bibliothèque C externe, ne l'a pas libéré après utilisation ou perfnodo-dealloc a échoué avec le timing de la durée de vie.

Avantages :

  • Intégration rapide avec l'API C

Inconvénients :

  • Fuites de mémoire jusqu'à épuisement de la RAM
  • Plantages dus à des accès après libération

Cas positif

Une enveloppe RAII avec Drop est utilisée, le pointeur est encapsulé avec Box ou NonNull, tout est détruit en toute sécurité à la fin de la portée.

Avantages :

  • Rust automatise la collecte des déchets dans l'objet finalisé
  • Risque minimal d'accès après libération

Inconvénients :

  • Parfois, cela nécessite d'encapsuler les allocations manuelles et un peu plus de boilerplate