October 24, 2025•6 min

Rust Vec::new() vs. with_capacity() : Quand utiliser chacune

m
mayo

Comprendre les stratégies d'allocation de Vec est crucial pour écrire du code Rust performant, particulièrement lorsqu'on travaille avec des collections et des itérateurs.

Vec::new() vs Vec::with_capacity(n) Vec::new() cap 0 cap 4 cap 8 cap 16 copie copie copie 4 réallocations with_capacity(16) cap 16 (1 alloc) push × 16 0 réallocation Connaître la taille finale à l'avance évite les cycles répétés de copie et libération

Différences principales

Vec::new() Vec::with_capacity(n)
Crée un Vec vide sans espace pré-alloué Crée un Vec vide avec de l'espace pour n éléments
La capacité initiale est 0 (alloue au premier push) La capacité initiale est exactement n (pas d'allocations précoces)
Croît dynamiquement (peut réallouer plusieurs fois) Évite la réallocation jusqu'à ce que len() > n

Lequel déclarer

Utilise Vec::new() quand :

  • Le nombre d'éléments est inconnu ou petit
  • Tu veux de la simplicité (ex : vecteurs de courte durée)
let mut v = Vec::new(); // Bon pour un usage ad hoc
v.push(1);

Utilise Vec::with_capacity(n) quand :

  • Tu connais le nombre exact ou maximum d'éléments à l'avance
  • Tu optimises pour la performance (évite les réallocations)
let mut v = Vec::with_capacity(1000); // Pré-alloue pour 1000 éléments
for i in 0..1000 {
    v.push(i); // Aucune réallocation ne se produit
}

Impact sur les performances

Vec::new() peut déclencher plusieurs réallocations lors de sa croissance (ex : commence à 0, puis 4, 8, 16, ...). Vec::with_capacity(n) garantit une seule allocation initiale (si n est correct).

Exemple de benchmark

use std::time::Instant;

fn main() {
    let start = Instant::now();
    let mut v1 = Vec::new();
    for i in 0..1_000_000 {
        v1.push(i); // Réalloue ~20 fois
    }
    println!("Vec::new(): {:?}", start.elapsed());

    let start = Instant::now();
    let mut v2 = Vec::with_capacity(1_000_000);
    for i in 0..1_000_000 {
        v2.push(i); // Aucune réallocation
    }
    println!("Vec::with_capacity(): {:?}", start.elapsed());
}

Sortie (typique) :

Vec::new(): 1.2ms
Vec::with_capacity(): 0.3ms  // 4x plus rapide

Notes avancées

  • shrink_to_fit() : Réduit la capacité excédentaire (ex : après suppression d'éléments)
  • Macro vec![] : Utilise with_capacity implicitement pour les littéraux (ex : vec![1, 2, 3])

Pré-allouer est une indication, pas une limite stricte. len et capacity sont deux nombres indépendants, et dès que len dépasserait capacity, le doublement habituel reprend :

Vie d'un Vec::with_capacity(4) : len vs capacity len 2 / cap 4 len 4 / cap 4 len 5 / cap 8 len 5 / cap 5 push × 2 5e push réalloc + copie shrink_to_fit() 2 libres 0 libre 3 libres 0 libre Plein = éléments vivants (len) · pointillé = réservé mais inutilisé (capacity − len) with_capacity(n) ne fait que retarder la croissance ; dépasser n relance le doublement
  • Par défaut, utilise Vec::new() pour la simplicité.
  • Utilise with_capacity(n) quand :
  • Tu connais la taille à l'avance
  • La performance est critique (ex : boucles critiques)

Pousser au-delà de la capacité réservée n'est pas une erreur : le Vec croît comme Vec::new() l'aurait fait depuis le début. Tu gagnes l'allocation unique jusqu'à n, et le doublement habituel ensuite.

Retour au blog
Partager ::