Creating a Vector of Zeros in Rust: Magic Under the Hood

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Vector of Zeros in Rust: Not as Simple as It Seems

\n\n

It would seem that nothing could be simpler than creating a vector filled with zeros. In Python, this is done in one line: [0] * n. In JavaScript — Array(n).fill(0). But when you switch to Rust, even such a trivial task opens up a whole world of systems programming, memory management, and zero-cost abstractions.

\n\n

The question "Creating a vector of zeros for a specific size" on Stack Overflow has gathered thousands of views. And this is no coincidence: behind a simple request lies a deep understanding of how Rust works with memory, initialization, and safety. Today, we will look under the hood of Rust and break down all the ways to create a vector of zeros, and most importantly, understand what actually happens in RAM.

\n\n

Why Is This Harder Than in Python?

\n\n

In Python or JavaScript, you simply create an array, and the interpreter does all the dirty work for you. Rust, however, is a systems-level language. It does not allow leaving memory uninitialized (unless you use unsafe). Every byte must be either explicitly filled or marked as "undefined behavior."

\n\n

Imagine: you order 100 empty boxes. In Python, you simply get 100 empty boxes delivered. In Rust, the courier will ask: "What should I put inside each one?" — and if you don't say, he just won't come. This guarantees that you will never accidentally read "garbage" from memory left over from a previous program.

\n\n

Method #1: The Classic vec![0; size]

\n\n

The most obvious and recommended way is to use the vec! macro:

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

Short, elegant, clear. But what happens under the hood? Let's break it down step by step.

\n\n

Under the Hood of vec![0; 10]

\n\n

When you write vec![0; 10], Rust does the following:

\n\n
    \n
  1. Allocates memory on the heap for 10 elements of type i32. Each i32 takes 4 bytes, totaling 40 bytes.
  2. \n
  3. Checks that the type implements the Clone trait. Numbers i32 implement it, so the compiler can copy the value 0 for each element.
  4. \n
  5. Runs a copy loop inside alloc::vec::from_elem. The compiler optimizes this loop to memset (if the type is a simple scalar).
  6. \n
  7. Returns a Vec with a length of 10 and a capacity of 10.
  8. \n
\n\n

The key point: memory is not left uninitialized. Rust guarantees that every byte will be filled with zeros before you gain access to the vector.

\n\n

Method #2: vec![Default::default(); size]

\n\n

If your type is not a number but implements the Default trait, you can use a generic approach:

\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

For primitive types, Default::default() gives 0, 0.0, false, or '\\0'. For custom structs — whatever you defined in #[derive(Default)].

\n\n

What About Performance?

\n\n

Let's write a simple benchmark (in real code, use 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

In

Blogs

Book Recommendations