Création d'un vecteur de zéros en Rust : la magie sous le capot

Formateur Python en ligne pour débutants

Apprenez Python facilement sans surcharge théorique. Résolvez des tâches pratiques avec vérification automatique et écrivez du code directement dans le navigateur.

Start Course

Vecteur de zéros en Rust : pas aussi simple qu'il n'y paraît

\n\n

On pourrait penser que quoi de plus simple que de créer un vecteur rempli de zéros ? En Python, cela se fait en une ligne : [0] * n. En JavaScript — Array(n).fill(0). Mais quand vous passez à Rust, même une tâche aussi triviale ouvre tout un monde de programmation système, de gestion de la mémoire et de coûts d'abstraction nuls.

\n\n

La question "Creating a vector of zeros for a specific size" sur Stack Overflow a rassemblé des milliers de vues. Et ce n'est pas un hasard : derrière une simple requête se cache une compréhension profonde de la façon dont Rust gère la mémoire, l'initialisation et la sécurité. Aujourd'hui, nous allons regarder sous le capot de Rust et analyser toutes les façons de créer un vecteur de zéros, et surtout — comprendre ce qui se passe réellement dans la mémoire vive.

\n\n

Pourquoi est-ce plus complexe qu'en Python ?

\n\n

En Python ou JavaScript, vous créez simplement un tableau, et l'interpréteur fait tout le travail à votre place. Rust, quant à lui, est un langage de niveau système. Il ne permet pas de laisser la mémoire non initialisée (sauf si vous utilisez unsafe). Chaque octet doit être soit explicitement rempli, soit marqué comme "comportement indéfini".

\n\n

Imaginez : vous commandez 100 boîtes vides. En Python, on vous livre simplement 100 boîtes vides. Avec Rust, le livreur demande : "Que dois-je mettre à l'intérieur de chacune ?" — et si vous ne le dites pas, il ne vient tout simplement pas. Cela garantit que vous ne lirez jamais accidentellement des "déchets" mémoire laissés par un programme précédent.

\n\n

Méthode n°1 : La classique vec![0; size]

\n\n

La méthode la plus évidente et recommandée est d'utiliser la macro vec! :

\n\n
let zeros: Vec<i32> = vec![0; 10];\nprintln!("{:?}", zeros); // [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
\n\n

Court, élégant, clair. Mais que se passe-t-il sous le capot ? Analysons étape par étape.

\n\n

Sous le capot de vec![0; 10]

\n\n

Quand vous écrivez vec![0; 10], Rust fait ce qui suit :

\n\n
    \n
  1. Alloue la mémoire sur le tas (heap) pour 10 éléments de type i32. Chaque i32 occupe 4 octets, soit 40 octets au total.
  2. \n
  3. Vérifie que le type implémente le trait Clone. Les nombres i32 l'implémentent, donc le compilateur peut copier la valeur 0 pour chaque élément.
  4. \n
  5. Lance une boucle de copie à l'intérieur de alloc::vec::from_elem. Le compilateur optimise cette boucle en memset (si le type est un scalaire simple).
  6. \n
  7. Retourne un Vec avec une longueur de 10 et une capacité de 10.
  8. \n
\n\n

Point clé : la mémoire ne reste pas non initialisée. Rust garantit que chaque octet sera rempli de zéros avant que vous n'ayez accès au vecteur.

\n\n

Méthode n°2 : vec![Default::default(); size]

\n\n

Si votre type n'est pas un nombre mais implémente le trait Default, vous pouvez utiliser une approche générique :

\n\n
#[derive(Default)]\nstruct Point {\n    x: f64,\n    y: f64,\n}\n\nlet points: Vec<Point> = vec![Default::default(); 5];\nprintln!("{:?}", points[0]); // Point { x: 0.0, y: 0.0 }
\n\n

Pour les types primitifs, Default::default() donne 0, 0.0, false ou '\\0'. Pour les structures personnalisées — ce que vous avez défini dans #[derive(Default)].

\n\n

Et qu'en est-il des performances ?

\n\n

Écrivons un simple benchmark (dans du code réel, utilisez criterion) :

\n\n
use std::time::Instant;\n\nfn main() {\n    let n = 1_000_000;\n\n    let start = Instant::now();\n    let v1 = vec![0u8; n];\n    let dur1 = start.elapsed();\n\n    let start = Instant::now();\n    let v2 = vec![0u64; n];\n    let dur2 = start.elapsed();\n\n    println!("u8:  {:?}", dur1);\n    println!("u64: {:?}", dur2);\n}
\n\n

En

Blogs

Book Recommendations