Top des erreurs des développeurs Rust débutants : niveau avancé
Rust — un langage qui promet une gestion sécurisée de la mémoire et des performances élevées. Cependant, lorsque vous passez des exemples de base aux projets réels, des erreurs commencent à apparaître qui ne se résolvent pas simplement en ajoutant & ou mut. Dans cet article, nous allons analyser quatre pièges avancés dans lesquels tombent souvent les développeurs qui maîtrisent déjà les bases du borrow checker et de la possession.
Cet article est destiné à ceux qui ont déjà écrit plusieurs projets en Rust et ont rencontré Rc, RefCell, les traits et unsafe. Nous n'expliquerons pas ce qu'est l'emprunt, mais nous nous concentrerons sur les pièges cachés.
1. Fuites mémoire via les références circulaires dans Rc/RefCell
L'erreur la plus insidieuse est de penser que Rc empêche automatiquement les fuites. En réalité, Rc utilise le comptage de références, et si deux objets se référencent mutuellement, le compteur ne reviendra jamais à zéro. La mémoire ne sera pas libérée.
Considérons un exemple classique avec un graphe :
use std::rc::Rc;use std::cell::RefCell;
#[derive(Debug)]struct Node { value: i32, children: Vec<Rc<RefCell<Node>>>,}
fn main() { let a = Rc::new(RefCell::new(Node { value: 1, children: vec![] })); let b = Rc::new(RefCell::new(Node { value: 2, children: vec![] }));
// Création d'une référence circulaire a.borrow_mut().children.push(Rc::clone(&b)); b.borrow_mut().children.push(Rc::clone(&a));
// Après la sortie de main, la mémoire ne sera pas libérée ! drop(a); drop(b); // Fuite : Rc pense qu'il y a encore des références}Pourquoi cela se produit-il ? Après drop(a), le compteur de références de a diminue, mais b contient toujours une référence à a via children. De même pour b. Finalement, les deux objets restent en mémoire.
Solution : Utilisez Weak<T> pour les références faibles. Dans les graphes, un nœud est généralement le propriétaire (référence forte), et les autres sont faibles.
use std::rc::{Rc, Weak};use std::cell::RefCell;
#[derive(Debug)]struct Node { value: i32, parent: RefCell<Weak<Node>>, // référence faible au parent children: RefCell<Vec<Rc<Node>>>, // références fortes aux enfants}
fn main() { let leaf = Rc::new(Node { value: 3, parent: RefCell::new(Weak::new()), children: RefCell::new(vec![]), });
let branch = Rc::new(Node { value: 5, parent: RefCell::new(Weak::new()), children: RefCell::new(vec![Rc::clone(&leaf)]), });
*leaf.parent.borrow_mut() = Rc::downgrade(&branch); // Maintenant, lors de la suppression de branch, leaf sera également libéré}2. Mauvaise utilisation de Pin pour les structures auto-référentielles
Les structures qui contiennent des références à elles-mêmes (self-referential) sont une cause fréquente de comportement indéfini. Rust ne permet pas de les créer directement, mais via Pin et unsafe, c'est possible. L'erreur est d'oublier que Pin garantit seulement que l'objet ne sera pas déplacé en mémoire, mais ne protège pas contre les décalages internes.
Exemple avec un future :
use std::pin::Pin;use std::marker::PhantomPinned;
#[derive(Debug)]struct SelfReferential { data: String, pointer: *const String, // pointeur brut vers data _pin: PhantomPinned,}
impl SelfReferential { fn new(data: String) -> Self { SelfReferential { pointer: std::ptr::null(), data, _pin: PhantomPinned, } }
fn init(self: Pin<&mut Self>) { let this = unsafe { self.get_unchecked_mut() }; this.pointer = &this.data as *const String; }}