Pourquoi le « ramasse-miettes » en Rust n'est pas ce que vous pensez
\n\nSi vous venez du monde Python, Java ou C#, le mot « ramasse-miettes » (garbage collector) évoque une image claire : un environnement en arrière-plan qui trouve et supprime périodiquement les objets auxquels plus personne ne fait référence. En Rust, c'est différent. Il n'y a pas de GC classique, mais il existe un système de possession et d'emprunt qui résout la même tâche — la gestion de la durée de vie des objets — au moment de la compilation.
\n\nCependant, la vie (et le code) est plus complexe que les schémas idéaux. Parfois, vous devez partager la possession d'un même objet entre plusieurs parties du programme, créer des références circulaires ou travailler avec des données mutables via des pointeurs immutables. C'est là qu'entrent en scène les objets garbage-collected en Rust : Rc, Arc, Weak et RefCell. Comprenons comment gérer leur durée de vie, éviter les fuites mémoire et ne pas se casser la tête.
Cet article est un guide détaillé pour ceux qui veulent comprendre comment fonctionne la gestion de la durée de vie des objets en Rust en pratique. Nous ne nous contenterons pas d'énumérer les types, mais nous montrerons des scénarios réels, y compris une question populaire sur Stack Overflow (oui, celle avec des milliers de vues).
\n\nConcepts clés : possession, emprunt et compteur de références
\n\nAvant de plonger en profondeur, rappelons les bases :
\n\n- \n
- Possession (ownership) — chaque valeur a exactement un propriétaire. Lorsque le propriétaire sort de la portée, la valeur est supprimée. \n
- Emprunt (borrowing) — vous pouvez temporairement « emprunter » une référence à une valeur sans en devenir le propriétaire. Restrictions : soit une référence mutable, soit autant de références immutables que vous le souhaitez. \n
- Compteur de références (reference counting) — c'est un mécanisme qui permet d'avoir plusieurs propriétaires d'une même valeur. En Rust, cela est implémenté via
Rc(pour les programmes monothread) etArc(pour les programmes multithread). \n
Lorsque nous parlons d'« objets garbage-collected » dans le contexte de Rust, nous faisons référence aux objets gérés par un compteur de références. Ils ne sont pas supprimés immédiatement après avoir quitté la portée, mais attendent que le compteur tombe à zéro. C'est ce qu'on appelle la gestion de la durée de vie — vous contrôlez quand la mémoire est libérée.
\n\nRc et Arc : la base de la gestion de la durée de vie
\n\nRc (Reference Counted) est un pointeur intelligent qui permet de partager la possession d'une même valeur entre plusieurs parties du code dans un seul thread. Chaque fois que vous clonez un Rc, le compteur augmente. Lorsque le clone sort de la portée, il diminue. Lorsque le compteur atteint zéro, la valeur est supprimée.
Exemple d'utilisation simple de Rc :
use std::rc::Rc;\n\nfn main() {\n let data = Rc::new(vec![1, 2, 3]);\n let a = Rc::clone(&data);\n let b = Rc::clone(&data);\n\n println!("Compteur de références : {}", Rc::strong_count(&data)); // 3\n\n drop(a);\n println!("Après suppression de a : {}", Rc::strong_count(&data)); // 2\n}\n\n\nIci, data, a et b sont trois propriétaires du même vecteur. Tant qu'au moins l'un d'eux est vivant, le vecteur reste en mémoire. C'est idéal pour les situations où vous ne pouvez pas déterminer quel module terminera en dernier.
Arc (Atomic Reference Counted) fonctionne de la même manière, mais utilise des opérations atomiques, ce qui le rend sûr pour les programmes multithread. Le prix est une légère baisse de performance.
use std::sync::Arc;\nuse std::thread;\n\nfn main() {\n let data = Arc::new(vec![1, 2, 3]);\n let mut handles = vec![];\n\n for _ in 0..3 {\n let data_clone = Arc::clone(&data);\n handles.push(thread::spawn(move || {\n println!("{:?}", data_clone);\n }));\n }\n\n for handle in handles {\n handle.join().unwrap();\n }\n}\n\n\n