August 29, 20253 min

Vec::retain() Vs filtrer avec iter().filter().collect() ?

m
mayo

Vec::retain(): Filtrage En Place

But : Supprime des éléments d'un Vec en place basé sur un prédicat, préservant l'ordre des éléments conservés.

Signature :

pub fn retain<F>(&mut self, f: F)
where
    F: FnMut(&T) -> bool,

Features Clés

Aspect retain() iter().filter().collect()
Mute l'Original ✅ Oui (en place) ❌ Non (alloue nouveau Vec)
Préserve l'Ordre ✅ Oui ✅ Oui
Efficacité Mémoire ✅ O(1) espace supplémentaire ❌ O(n) espace supplémentaire
Performance Plus rapide (pas de réallocation) Plus lent (alloue/copie)
Cas d'Usage Filtrage sans allocation Créer une nouvelle collection filtrée

Exemple : Filtrer les Nombres Pairs

Utiliser retain() (En Place)

let mut vec = vec![1, 2, 3, 4];
vec.retain(|x| x % 2 == 0);  // Garde les pairs
assert_eq!(vec, [2, 4]);      // `vec` original modifié

Utiliser filter().collect() (Nouvelle Allocation)

let vec = vec![1, 2, 3, 4];
let filtered: Vec<_> = vec.iter().filter(|x| *x % 2 == 0).copied().collect();
assert_eq!(filtered, [2, 4]);  // Nouveau `Vec` créé
// `vec` reste inchangé : [1, 2, 3, 4]

Comparaison de Performance

retain() :

  • Temps : O(n) (passe unique, décale les éléments à gauche en place).
  • Espace : O(1) (pas d'allocations supplémentaires).

filter().collect() :

  • Temps : O(n) (mais nécessite copie vers une nouvelle allocation).
  • Espace : O(n) (nouveau Vec alloué).

Suggestion de Benchmark :

let mut big_vec = (0..1_000_000).collect::<Vec<_>>();

// Mesurer `retain`
let start = std::time::Instant::now();
big_vec.retain(|x| x % 2 == 0);
println!("retain: {:?}", start.elapsed());

// Mesurer `filter().collect()`
let big_vec = (0..1_000_000).collect::<Vec<_>>();
let start = std::time::Instant::now();
let filtered = big_vec.iter().filter(|x| *x % 2 == 0).collect::<Vec<_>>();
println!("filter.collect: {:?}", start.elapsed());

Résultat Typique : retain() est 2–3x plus rapide à cause de l'absence d'allocations.

Quand Utiliser Chacun

Préfère retain() Quand :

  • Tu veux modifier le Vec en place.
  • L'efficacité mémoire est critique (ex : gros Vecs).
  • L'ordre des éléments doit être préservé.

Préfère filter().collect() Quand :

  • Tu as besoin que le Vec original reste intact.
  • Tu chaînes plusieurs adaptateurs d'iterator (ex : .filter().map()).
  • Tu travailles avec des iterators non-Vec (ex : ranges, slices).

Notes Avancées

retain_mut() :

Rust fournit aussi retain_mut() pour des prédicats qui ont besoin d'accès mutable aux éléments :

let mut vec = vec![1, 2, 3];
vec.retain_mut(|x| {
    *x += 1;           // Modifie en place
    *x % 2 == 0        // Garde si pair après incrément
});
assert_eq!(vec, [2, 4]);

Stabilité :

Les deux méthodes préservent l'ordre relatif des éléments conservés (filtrage stable).

Points Clés

retain() : Plus rapide, efficace en mémoire, et en place. Idéal pour modifications en masse.

filter().collect() : Flexible, non-destructif. Idéal pour pipelines d'iterator.

Cas d'Usage Réel :

  • retain() : Nettoyer les sessions expirées dans un pool de sessions de serveur.
  • filter().collect() : Transformer les données de réponse API en sous-ensemble filtré.

Essaie Ceci : Que se passe-t-il si tu fais retain() avec un prédicat qui garde tous les éléments ?

Réponse : No-op (aucun élément supprimé, pas de réallocations).

Retour au blog
Partager ::