Stack vs Heap
Comment Rust gère l'allocation mémoire
Deux régions de mémoire
Quand un programme s'exécute, il utilise deux régions principales de la mémoire RAM : la pile (stack) et le tas (heap). Comprendre la différence est essentiel pour écrire du code Rust performant.
Mémoire d'un processus : ┌─────────────────────────┐ adresses hautes │ Pile (Stack) │ ← grandit vers le bas │ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ │ │ │ │ (espace libre) │ │ │ │ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ │ │ Tas (Heap) │ ← grandit vers le haut ├─────────────────────────┤ │ Données statiques │ ├─────────────────────────┤ │ Code du programme │ └─────────────────────────┘ adresses basses
La pile et le tas ont des caractéristiques très différentes en termes de vitesse, de taille et de gestion. Rust vous donne un contrôle fin sur l'endroit où vos données sont stockées.
La pile (Stack)
La pile fonctionne selon le principe LIFO (Last In, First Out) : le dernier élément empilé est le premier à être dépilé. C'est exactement comme une pile d'assiettes.
Avantages de la pile
- - Extrêmement rapide : un simple déplacement de pointeur
- - Allocation et désallocation en temps constant O(1)
- - Données proches en mémoire (cache-friendly)
- - Nettoyage automatique quand la fonction retourne
Limites de la pile
- - Taille limitée (typiquement 1 à 8 Mo)
- - Données de taille fixe uniquement (connue à la compilation)
- - Durée de vie liée à la portée de la fonction
Appels de fonctions sur la pile :
fn main() fn calcul() Retour de calcul()
┌──────────┐ ┌──────────┐ ┌──────────┐
│ │ │ résultat │ │ │
│ │ │ y = 20 │ │ │
│ │ │ x = 10 │ │ │
├──────────┤ ├──────────┤ ├──────────┤
│ a = 5 │ │ a = 5 │ │ a = 5 │
│ b = 3 │ │ b = 3 │ │ b = 3 │
└──────────┘ └──────────┘ └──────────┘
↑ SP ↑ SP ↑ SP remonte
(Stack Pointer) Frame dépilée !Le tas (Heap)
Le tas est une région de mémoire plus grande et plus flexible. L'allocation se fait via l'allocateur du système (comme malloc en C). C'est plus lent, mais les données peuvent avoir une taille dynamique et survivre au-delà de la portée d'une fonction.
Allocation sur le tas :
Pile (Stack) Tas (Heap)
┌──────────────┐ ┌──────────┬─────┬──────────┐
│ ptr ─────────────────→ │ "Bonjour le monde" │
│ len = 18 │ ├──────────┤ │ │
│ cap = 32 │ │ (libre) │ │ │
└──────────────┘ ├──────────┤ │ │
│ vec data ├─────┘ │
Le pointeur, la │ [1,2,3] │ │
longueur et la └──────────┴────────────────┘
capacité sont sur
la pile. Les données Les données sont sur le tas.
sont sur le tas. L'allocateur gère l'espace.Avantages du tas
- - Taille dynamique : peut grandir et rétrécir
- - Beaucoup plus grand que la pile (limité par la RAM)
- - Données partageables entre plusieurs parties du programme
Limites du tas
- - Plus lent : l'allocateur doit chercher un bloc libre
- - Fragmentation possible de la mémoire
- - Requiert une gestion manuelle ou un GC (sauf en Rust)
Rust sur la pile
Par défaut, Rust place les données sur la pile quand leur taille est connue à la compilation. C'est le cas des types primitifs et des structures de taille fixe :
fn main() {
// Tous ces types vivent sur la pile
let entier: i32 = 42; // 4 octets
let flottant: f64 = 3.14; // 8 octets
let booleen: bool = true; // 1 octet
let caractere: char = 'R'; // 4 octets (Unicode)
let tuple: (i32, f64) = (1, 2.0); // 12 octets
let tableau: [i32; 5] = [1, 2, 3, 4, 5]; // 20 octets
// Les structs de taille fixe aussi
struct Point { x: f64, y: f64 }
let p = Point { x: 1.0, y: 2.0 }; // 16 octets sur la pile
}La pile après ces déclarations : ┌─────────────────────────────────┐ │ p.y = 2.0 (f64) │ │ p.x = 1.0 (f64) │ │ tableau = [1,2,3,4,5] (20B) │ │ tuple = (1, 2.0) (12B) │ │ caractere = 'R' (4B) │ │ booleen = true (1B) │ │ flottant = 3.14 (8B) │ │ entier = 42 (4B) │ └─────────────────────────────────┘ ↑ Stack Pointer Tout est contigu en mémoire → cache CPU efficace
Ces types implémentent le trait Copy : ils sont copiés automatiquement lors d'une affectation, plutôt que déplacés (moved). La copie est rapide car les données sont petites et sur la pile.
Rust sur le tas
Quand les données ont une taille dynamique ou doivent survivre au-delà d'une portée, Rust les alloue sur le tas. Les trois types les plus courants sont :
fn main() {
// String : texte de taille dynamique
let mut s = String::from("bonjour");
s.push_str(" le monde"); // peut grandir !
// Vec<T> : tableau dynamique
let mut v: Vec<i32> = Vec::new();
v.push(1);
v.push(2);
v.push(3); // grandit automatiquement
// Box<T> : valeur unique sur le tas
let b = Box::new(42); // un i32 sur le tas
}Pile (Stack) Tas (Heap) ┌──────────────┐ │ b │ ┌──────┐ │ ptr ──────────────→ │ 42 │ ├──────────────┤ └──────┘ │ v │ ┌───┬───┬───┬───┐ │ ptr ──────────────→ │ 1 │ 2 │ 3 │ │ │ len = 3 │ └───┴───┴───┴───┘ │ cap = 4 │ capacité = 4 ├──────────────┤ │ s │ ┌─────────────────┐ │ ptr ──────────────→ │ bonjour le monde│ │ len = 16 │ └─────────────────┘ │ cap = 32 │ └──────────────┘ Métadonnées (ptr, len, cap) sur la pile. Données réelles sur le tas.
Quand ces variables sortent de la portée, Rust appelle automatiquement drop() qui libère la mémoire du tas. Pas besoin de free() ni de garbage collector.
fn creer_vecteur() -> Vec<i32> {
let v = vec![1, 2, 3]; // alloué sur le tas
v // propriété transférée à l'appelant
} // si v n'était pas retourné, drop() serait appelé ici
fn main() {
let donnees = creer_vecteur();
println!("{:?}", donnees);
} // drop() libère la mémoire iciRésumé
Rust vous donne un contrôle explicite sur l'emplacement mémoire de vos données, tout en gérant la désallocation automatiquement.
| Caractéristique | Pile (Stack) | Tas (Heap) |
|---|---|---|
| Vitesse | Tres rapide | Plus lent |
| Taille | Fixe (compilation) | Dynamique |
| Durée de vie | Portée de la fonction | Contrôlée par ownership |
| Types Rust | i32, f64, bool, [T; N] | String, Vec<T>, Box<T> |
| Gestion | Automatique (LIFO) | Automatique (drop) |
Contrairement a C ou il faut appeler free() manuellement, et contrairement a Java qui utilise un garbage collector, Rust libère la mémoire du tas au moment exact ou le propriétaire sort de la portée. C'est le meilleur des deux mondes : le contrôle du C avec la sécurité d'un langage managé.