Question

How to Create a Zero-Filled Vector of Dynamic Size in Rust

Question

I want to initialize a vector of zeros in Rust, where the size is only known at runtime.

In C, I might write something like this:

int main(void)
{
    unsigned int size = get_uchar();
    int A[size][size];
    memset(A, 0, size * size * sizeof(int));
}

In Rust, I tried writing a helper function like this:

fn zeros(size: u32) -> Vec<i32> {
    let mut zero_vec: Vec<i32> = Vec::with_capacity(size);
    for i in 0..size {
        zero_vec.push(0);
    }
    return zero_vec;
}

I suspect the 0..size range or the argument type is causing compiler issues. This also feels more verbose than the C version. Is there a more idiomatic way to create a vector filled with zeros?

I eventually want to use it in code like this:

pub fn kmp(text: &str, pattern: &str) -> i64 {
    let mut shifts = zeros(pattern.len() + 1);
}

Short Answer

By the end of this page, you will understand how to create a Vec filled with repeated values in Rust, especially when the length is only known at runtime. You will also learn why Vec::with_capacity() does not create elements, how to use the idiomatic vec![value; size] syntax, and how this applies in real code such as search algorithms.

Concept

In Rust, a Vec<T> has two important properties:

  • length: how many actual elements are currently in the vector
  • capacity: how much memory has been reserved

This distinction is the key idea behind the original question.

When you call:

Vec::with_capacity(size)

you only reserve memory for future elements. The vector is still empty, so its length is 0. That means Rust does not consider it to contain any usable values yet.

To create a vector that already contains size elements, all set to zero, use the idiomatic macro:

vec![0; size]

This creates a Vec with size elements, each initialized to 0.

This matters because Rust is strict about initialized memory. Unlike low-level C patterns such as memset, Rust encourages safe construction of fully initialized values. Instead of allocating raw memory and then manually zeroing it, you typically create the values directly.

For numeric types like i32, zero-filling is common and efficient. For many real programs, this pattern is used for:

  • counters n- lookup tables
  • dynamic programming arrays
  • prefix tables for string algorithms
  • temporary buffers

In your KMP example, a zero-filled vector is a natural fit for storing shift or prefix information.

Mental Model

Think of a vector like a row of labeled storage boxes.

  • Capacity means you bought shelf space for 10 boxes.
  • Length means how many boxes actually have items in them.

Vec::with_capacity(10) means:

  • you reserved space for 10 boxes
  • but none of the boxes contain an item yet

vec![0; 10] means:

  • you created 10 boxes
  • each box already contains 0

So if you need actual usable values right away, you want a vector with a real length, not just extra capacity.

Take Quiz

Syntax and Examples

The most idiomatic syntax is:

let values = vec![0; size];

This creates a Vec containing size copies of 0.

Example: create a zero-filled vector

fn zeros(size: usize) -> Vec<i32> {
    vec![0; size]
}

Usage:

fn main() {
    let a = zeros(5);
    println!("{:?}", a);
}

Output:

[0, 0, 0, 0, 0]

Why usize instead of u32?

Vector sizes and indices in Rust use usize, because it matches the platform's native pointer-sized integer.

For example:

fn zeros(size: usize) -> Vec<i32> {
    vec![0; size]
}

This works naturally with pattern.len() because len() returns usize.

Your KMP example

pub fn kmp(text: &str, pattern: &str) -> i64 {
    let mut shifts: Vec<i32> = vec![0; pattern.len() + 1];
    0
}

If you still want a helper function:

fn zeros(size: usize) -> Vec<i32> {
    vec![0; size]
}

pub fn kmp(text: &str, pattern: &str) -> i64 {
    let mut shifts = zeros(pattern.len() + 1);
    0
}

Alternative: using an iterator

You can also write:

let values: Vec<i32> = std::iter::repeat(0).take(size).collect();

This is valid, but vec![0; size] is simpler and more idiomatic for this case.

Step by Step Execution

Consider this code:

fn main() {
    let size = 4;
    let numbers = vec![0; size];
    println!("{:?}", numbers);
}

Step by step:

  1. size is set to 4.
  2. vec![0; size] tells Rust to create a vector.
  3. Rust allocates enough space for 4 elements.
  4. Rust fills each element with 0.
  5. numbers becomes:
[0, 0, 0, 0]
  1. println! prints the vector.

Now compare that to this:

let numbers: Vec<i32> = Vec::with_capacity(4);

Step by step:

  1. Rust allocates enough memory for up to 4 i32 values.
  2. The vector length is still 0.
  3. There are no actual elements yet.
  4. If you print it, you get:
[]
  1. You must push elements yourself before using them.

That is the core difference: capacity reserves space, length creates elements.

Real World Use Cases

Zero-filled vectors appear often in practical Rust programs.

Counting and frequency tables

let mut counts = vec![0; 26];

Useful for:

  • letter frequency
  • histogram buckets
  • event counters

Dynamic programming

let dp = vec![0; n + 1];

Useful for:

  • path counting
  • coin change
  • edit distance

String matching algorithms

let prefix = vec![0; pattern.len()];

Useful for:

  • KMP prefix table
  • failure function arrays

Buffers for processing data

let buffer = vec![0u8; 1024];

Useful for:

  • reading bytes
  • temporary storage
  • network packet handling

Grid or matrix-like storage

For a 2D structure stored in one vector:

let width = 5;
let height = 5;
let grid = vec![0; width * height];

Useful for:

  • game boards
  • image processing
  • simulations
Take Quiz

Real Codebase Usage

In real Rust codebases, developers usually choose one of these patterns depending on intent.

1. Create fully initialized data

let visited = vec![false; node_count];
let distances = vec![0; node_count];

This is the standard choice when every element should exist immediately.

2. Reserve space for future pushes

let mut items = Vec::with_capacity(expected_count);
for value in source {
    items.push(value);
}

This is useful when you know roughly how many items will be added, but you do not want placeholder elements.

3. Use guard clauses before allocation

fn build_table(size: usize) -> Vec<i32> {
    if size == 0 {
        return Vec::new();
    }
    vec![0; size]
}

This can make special cases explicit, although vec![0; 0] is already valid.

4. Initialize based on configuration

let retries = config.max_retries;
let mut retry_counts = vec![0; retries];

A common pattern when sizes come from input, configuration files, or API responses.

5. Fill then mutate

let mut prefix = vec![0; pattern.len()];
prefix[0] = 1;

This is common in algorithms where a default value is needed before selective updates.

Common Mistakes

Mistake 1: Confusing capacity with length

Broken example:

let mut v: Vec<i32> = Vec::with_capacity(5);
println!("{}", v[0]);

This fails because the vector has length 0, so index 0 does not exist.

Use this instead:

let v = vec![0; 5];
println!("{}", v[0]);

Mistake 2: Using u32 for sizes

Broken example:

fn zeros(size: u32) -> Vec<i32> {
    vec![0; size]
}

This does not match what Vec expects. Vector lengths use usize.

Correct version:

fn zeros(size: usize) -> Vec<i32> {
    vec![0; size]
}

Mistake 3: Thinking [0; size] creates a Vec

let arr = [0; 5];

This creates an array, not a vector. Arrays have fixed size known at compile time.

For a runtime-sized vector, use:

let v = vec![0; size];

Mistake 4: Writing unnecessary return

This is valid Rust:

fn zeros(size: usize) -> Vec<i32> {
    return vec![0; size];
}

But idiomatic Rust usually omits return for the final expression:

fn zeros(size: usize) -> Vec<i32> {
    vec![0; size]
}

Mistake 5: Using a loop when a direct initializer exists

Verbose version:

fn zeros(size: usize) -> Vec<i32> {
    let mut v = Vec::with_capacity(size);
    for _ in 0..size {
        v.push(0);
    }
    v
}

This works, but the simpler and clearer version is:

fn zeros(size: usize) -> Vec<i32> {
    vec![0; size]
}

Comparisons

ApproachWhat it doesBest use caseNotes
vec![0; size]Creates a vector of length size, filled with 0When you want initialized elements immediatelyMost idiomatic for this problem
Vec::with_capacity(size)Reserves memory for up to size elementsWhen you plan to push values laterLength stays 0
std::iter::repeat(0).take(size).collect()Builds a vector from repeated valuesWhen working in iterator-heavy codeMore verbose here
[0; N]Creates a fixed-size arrayWhen size is known at compile timeNot for runtime-sized vectors

Array vs Vector

FeatureArrayVec
Size known at compile timeYesNot required
Can grow or shrinkNoYes
Runtime-sized initializationNoYes
Syntax example[0; 4]vec![0; size]

C vs Rust mindset

C styleRust style
Allocate raw memory, then zero itCreate initialized values directly
memset often usedvec![0; size] often used
Manual memory reasoningSafer high-level initialization
Take Quiz

Cheat Sheet

// Idiomatic zero-filled vector
let v = vec![0; size];

// Helper function
fn zeros(size: usize) -> Vec<i32> {
    vec![0; size]
}

// Reserve capacity only (does NOT create elements)
let mut v: Vec<i32> = Vec::with_capacity(size);

// Add elements manually after reserving capacity
for _ in 0..size {
    v.push(0);
}

// len() returns usize, so helper functions should usually take usize
fn zeros(size: usize) -> Vec<i32> {
    vec![0; size]
}

Key rules

  • Use vec![value; size] to create repeated values.
  • Use Vec::with_capacity(size) only when you plan to push later.
  • pattern.len() returns usize.
  • [0; N] is an array, not a vector.
  • vec![0; 0] is valid and creates an empty vector.

Good default for this question

let shifts = vec![0; pattern.len() + 1];

FAQ

How do I create a vector of zeros in Rust?

Use:

let v = vec![0; size];

This creates a vector with size elements, each set to 0.

Why doesn't Vec::with_capacity() fill the vector with zeros?

Because it only reserves memory. It increases capacity, not length. The vector is still empty until you add elements.

Should I use u32 or usize for vector sizes?

Use usize. Rust collections and indexing APIs use usize for lengths and indices.

What is the difference between [0; size] and vec![0; size]?

[0; size] creates an array, usually for compile-time known sizes. vec![0; size] creates a heap-allocated vector that can use a runtime size.

Is vec![0; size] efficient?

Yes. It is the normal and idiomatic way to create a vector filled with the same value.

Can I use this for other default values besides zero?

Yes.

let flags = vec![false; 10];
let names = vec![String::new(); 3];

How should I write the KMP example?

A straightforward version is:

let mut shifts = vec![0; pattern.len() + 1];

This matches Rust's collection conventions and works with runtime sizes.

Take Quiz

Mini Project

Description

Build a small Rust utility that creates and updates a frequency table for lowercase English letters. This demonstrates how zero-filled vectors are used in real programs to store counters whose size is known only when the program runs.

Goal

Create a function that counts how many times each lowercase letter appears in a string using a zero-initialized vector.

Requirements

  • Create a vector of 26 zeros to represent letter counts.
  • Iterate through the input string one character at a time.
  • Update the correct counter for each lowercase letter.
  • Ignore characters that are not lowercase English letters.
  • Print or return the final counts.
Take Quiz

Keep learning

Related questions

View more