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.
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:
sizeis set to4.vec![0; size]tells Rust to create a vector.- Rust allocates enough space for 4 elements.
- Rust fills each element with
0. numbersbecomes:
[0, 0, 0, 0]
println!prints the vector.
Now compare that to this:
let numbers: Vec<i32> = Vec::with_capacity(4);
Step by step:
- Rust allocates enough memory for up to 4
i32values. - The vector length is still
0. - There are no actual elements yet.
- If you print it, you get:
[]
- You must
pushelements 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
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
| Approach | What it does | Best use case | Notes |
|---|---|---|---|
vec![0; size] | Creates a vector of length size, filled with 0 | When you want initialized elements immediately | Most idiomatic for this problem |
Vec::with_capacity(size) | Reserves memory for up to size elements | When you plan to push values later | Length stays 0 |
std::iter::repeat(0).take(size).collect() | Builds a vector from repeated values | When working in iterator-heavy code | More verbose here |
[0; N] | Creates a fixed-size array | When size is known at compile time | Not for runtime-sized vectors |
Array vs Vector
| Feature | Array | Vec |
|---|---|---|
| Size known at compile time | Yes | Not required |
| Can grow or shrink | No | Yes |
| Runtime-sized initialization | No | Yes |
| Syntax example | [0; 4] | vec![0; size] |
C vs Rust mindset
| C style | Rust style |
|---|---|
| Allocate raw memory, then zero it | Create initialized values directly |
memset often used | vec![0; size] often used |
| Manual memory reasoning | Safer high-level initialization |
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 topushlater. pattern.len()returnsusize.[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.
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.
Keep learning
Related questions
Absolute Values in Rust: Using the abs() Method
Learn how to calculate absolute values in modern Rust with abs(), handle signed integer overflow, and choose safer alternatives.
Accepting Iterators in Rust Functions with impl Iterator
Learn how to write Rust functions that accept HashMap iterators using generic bounds or impl Iterator, avoiding trait object and lifetime errors.
Access Enum Variant Values in Rust with Pattern Matching
Learn how to extract and print values stored in Rust enum variants using match and if let pattern matching, with practical examples.