Question

How to Iterate a Vec with Its Index in Rust

rustfor-loopiterator

Question

I need to iterate over a Vec and access both the current element and its position in the vector. I assume Rust already provides this in the standard API, but I have not found it.

I want something like this:

fn main() {
    let v = vec![1; 10];

    for (pos, e) in v.iter() {
        // do something here
    }
}

How can I iterate through a Vec<T> while also getting the index of each element?

Short Answer

By the end of this page, you will understand how to iterate over a Vec<T> in Rust while also getting each element's index. You will learn to use .enumerate(), see how it works with .iter(), .iter_mut(), and .into_iter(), and understand common mistakes beginners make when working with indexed iteration.

Concept

In Rust, iterating over a collection and also tracking the position of each item is a very common task. The standard way to do this is with the .enumerate() adapter.

enumerate() takes any iterator and transforms it into a new iterator that yields pairs:

  • the current index
  • the current item

For a Vec<T>, you usually start with one of these iterator methods:

  • .iter() gives immutable references like &T
  • .iter_mut() gives mutable references like &mut T
  • .into_iter() gives owned values like T

Then you add .enumerate() to include the index.

Example:

let v = vec![10, 20, 30];
for (index, value) in v.iter().enumerate() {
    println!("{} -> {}", index, value);
}

This matters because many real programs need both the data and its position, for example:

  • showing numbered lists in a UI
  • updating items by position
  • validating rows in imported data
  • formatting output with line numbers
  • comparing an element with its index

Instead of manually tracking a counter, Rust encourages using iterator adapters like enumerate() because they are clear, safe, and idiomatic.

Mental Model

Think of .iter() as a person handing you each item from a row of boxes.

Normally, you only get the item:

  • box content: 10
  • box content: 20
  • box content: 30

When you add .enumerate(), that person also tells you which box number it came from:

  • box 0: 10
  • box 1: 20
  • box 2: 30

So enumerate() is like attaching a label number to every item as it comes through the iterator.

It does not change the vector itself. It just changes what the iterator produces.

Take Quiz

Syntax and Examples

The most common syntax is:

for (index, item) in vec.iter().enumerate() {
    // use index and item
}

Basic example

fn main() {
    let v = vec![1, 2, 3];

    for (pos, e) in v.iter().enumerate() {
        println!("position: {}, value: {}", pos, e);
    }
}

Output:

position: 0, value: 1
position: 1, value: 2
position: 2, value: 3

Here:

  • v.iter() produces &i32
  • .enumerate() turns that into (usize, &i32)
  • pos is the index
  • e is a reference to the element

Modifying values with iter_mut()

fn main() {
    let mut v = vec![10, 20, 30];

    for (pos, value) in v.iter_mut().enumerate() {
        *value += pos as i32;
    }

    println!("{:?}", v);
}

Output:

[10, 21, 32]

Here:

  • iter_mut() gives mutable references
  • value is &mut i32
  • *value is used to change the actual vector element

Taking ownership with into_iter()

fn main() {
    let v = vec!["a", "b", "c"];

    for (pos, value) in v.into_iter().enumerate() {
        println!("{}: {}", pos, value);
    }
}

This consumes v, so you cannot use v afterward.

Direct answer to the question

fn main() {
    let v = vec![1; 10];

    for (pos, e) in v.iter().enumerate() {
        println!("{} -> {}", pos, e);
    }
}

Step by Step Execution

Consider this example:

fn main() {
    let v = vec![5, 6, 7];

    for (pos, e) in v.iter().enumerate() {
        println!("pos = {}, e = {}", pos, e);
    }
}

Let’s trace it.

Step 1: Create the vector

let v = vec![5, 6, 7];

v is a Vec<i32> containing three elements.

Step 2: Call iter()

v.iter()

This creates an iterator over references:

  • &5
  • &6
  • &7

Step 3: Call enumerate()

v.iter().enumerate()

Now each yielded value becomes a pair:

  • (0, &5)
  • (1, &6)
  • (2, &7)

The index type is usize.

Step 4: Pattern matching in the for loop

for (pos, e) in ...

Each pair is unpacked into:

  • pos gets 0, then 1, then 2
  • e gets &5, then &6, then &7

Step 5: Print each pair

First iteration:

pos = 0, e = 5

Second iteration:

pos = 1, e = 6

Third iteration:

pos = 2, e = 7

Important detail

e is a reference because iter() was used. Rust lets println! display it naturally, but if you need the value itself in some contexts, remember you may be working with &T rather than T.

Real World Use Cases

Indexed iteration appears in many practical Rust programs.

Display numbered results

let tasks = vec!["build", "test", "deploy"];
for (i, task) in tasks.iter().enumerate() {
    println!("{}. {}", i + 1, task);
}

Useful for:

  • CLI menus
  • todo lists
  • ranked output

Validate imported rows

let rows = vec!["Alice", "", "Charlie"];
for (i, row) in rows.iter().enumerate() {
    if row.is_empty() {
        println!("Row {} is empty", i);
    }
}

Useful for:

  • CSV import tools
  • parsers
  • batch processing scripts

Update elements based on position

let mut data = vec![0, 0, 0, 0];
for (i, value) in data.iter_mut().enumerate() {
    *value = i as i32;
}

Useful for:

  • initializing arrays
  • assigning sequence numbers
  • generating test data

Format logs with line numbers

let lines = vec!["start", "load config", "done"];
for (i, line) in lines.iter().enumerate() {
    println!("[{}] {}", i, line);
}

Useful for:

  • debugging
  • reporting
  • text processing
Take Quiz

Real Codebase Usage

In real Rust codebases, developers often combine enumerate() with other iterator tools instead of writing manual counters.

Common pattern: immutable read with index

for (i, item) in items.iter().enumerate() {
    println!("{}: {:?}", i, item);
}

Used for reporting, debugging, and rendering.

Common pattern: mutate each element with its index

for (i, item) in items.iter_mut().enumerate() {
    *item += i as i32;
}

Used in data transformation and preprocessing.

Common pattern: skip or filter while keeping original positions

for (i, item) in items.iter().enumerate() {
    if item.is_empty() {
        continue;
    }
    println!("{} => {}", i, item);
}

This is useful when indexes matter for error messages or traceability.

Guard clauses and validation

for (i, value) in inputs.iter().enumerate() {
    if *value < 0 {
        println!("Invalid value at index {}", i);
        break;
    }
}

This pattern appears in validation code, parsers, and API input checking.

Iterator pipelines

enumerate() can also be part of a chain:

let even_positions: Vec<_> = v
    .iter()
    .enumerate()
    .filter(|(i, _)| i % 2 == 0)
    .map(|(_, value)| *value)
    .collect();

This style is common in production Rust because it is expressive and avoids extra state variables.

Common Mistakes

1. Forgetting to call enumerate()

Broken code:

fn main() {
    let v = vec![1, 2, 3];

    for (pos, e) in v.iter() {
        println!("{} {}", pos, e);
    }
}

Why it fails:

  • v.iter() yields only one thing each time: &i32
  • the loop expects a pair like (pos, e)

Fix:

for (pos, e) in v.iter().enumerate() {
    println!("{} {}", pos, e);
}

2. Confusing references with values

With .iter(), the item is a reference.

let v = vec![10, 20, 30];
for (i, value) in v.iter().enumerate() {
    println!("{} -> {}", i, value);
}

This works fine for printing, but remember value is &i32, not i32.

If you need an owned value for a Copy type:

for (i, value) in v.iter().enumerate() {
    let n = *value;
    println!("{} -> {}", i, n);
}

3. Using into_iter() and then trying to use the vector again

Broken code:

fn main() {
    let v = vec![1, 2, 3];

    for (i, value) in v.into_iter().enumerate() {
        println!("{} -> {}", i, value);
    }

    println!("{:?}", v);
}

Why it fails:

  • into_iter() consumes the vector
  • ownership moves into the loop

Fix: use .iter() if you still need the vector later.

for (i, value) in v.iter().enumerate() {
    println!("{} -> {}", i, value);
}
println!("{:?}", v);

4. Forgetting to dereference with iter_mut()

Broken code:

let mut v = vec![1, 2, 3];
for (i, value) in v.iter_mut().enumerate() {
    value += i as i32;
}

Why it fails:

  • value is &mut i32
  • you must modify the pointed-to value

Fix:

for (i, value) in v.iter_mut().enumerate() {
    *value += i as i32;
}

5. Assuming indexes start at 1

enumerate() starts at 0, not 1.

If you want human-friendly numbering:

for (i, item) in v.iter().enumerate() {
    println!("Item {}: {}", i + 1, item);
}

Comparisons

ApproachWhat it gives youConsumes vector?Common use
v.iter()&TNoRead items without index
v.iter().enumerate()(usize, &T)NoRead items with index
v.iter_mut().enumerate()(usize, &mut T)NoModify items with index
v.into_iter().enumerate()(usize, T)YesTake ownership with index

enumerate() vs manual counter

Manual counter

let mut i = 0;
for value in v.iter() {
    println!("{} -> {}", i, value);
    i += 1;
}

Idiomatic Rust

for (i, value) in v.iter().enumerate() {
    println!("{} -> {}", i, value);
}

Why enumerate() is better:

  • shorter
  • clearer
  • less error-prone
  • avoids managing extra mutable state

enumerate() vs indexing with a range

Using a range

for i in 0..v.len() {
    println!("{} -> {}", i, v[i]);
}

Using enumerate()

for (i, value) in v.iter().enumerate() {
    println!("{} -> {}", i, value);
}

enumerate() is often preferred because:

  • it works directly with iterators
  • it avoids repeated indexing
  • it is safer and more idiomatic
  • it fits well with iterator chains
Take Quiz

Cheat Sheet

Indexed iteration in Rust

Read-only iteration

for (i, item) in v.iter().enumerate() {
    println!("{} -> {}", i, item);
}
  • i is usize
  • item is &T

Mutable iteration

for (i, item) in v.iter_mut().enumerate() {
    *item = i as i32;
}
  • item is &mut T
  • use *item to modify the value

Owning iteration

for (i, item) in v.into_iter().enumerate() {
    println!("{} -> {:?}", i, item);
}
  • item is T
  • v is consumed

Key rule

enumerate() turns:

Iterator<Item = X>

into:

Iterator<Item = (usize, X)>

Common fix for the original problem

for (pos, e) in v.iter().enumerate() {
    // use pos and e
}

Watch out for

  • enumerate() starts at 0
  • .iter() gives references
  • .into_iter() moves ownership
  • .iter_mut() requires dereferencing with *item

FAQ

How do I get the index while iterating over a vector in Rust?

Use .enumerate() after creating an iterator:

for (i, item) in v.iter().enumerate() {
    // ...
}

Does enumerate() work only with Vec?

No. It works with any iterator, including iterators from slices, arrays, strings, and custom iterator types.

Why is the index type usize?

Because indexes in Rust collections use usize, which is the platform-sized unsigned integer type used for memory indexing.

What is the difference between iter() and into_iter() with enumerate()?

  • iter() gives references like &T
  • into_iter() gives owned values like T and consumes the collection

Can I start counting from 1 instead of 0?

enumerate() always starts at 0. If you want display numbering from 1, use i + 1 when printing or formatting.

Can I modify elements while also using the index?

Yes. Use .iter_mut().enumerate().

for (i, item) in v.iter_mut().enumerate() {
    *item += i as i32;
}

Is using enumerate() better than indexing with 0..v.len()?

Usually yes. It is more idiomatic, works naturally with iterators, and avoids manual indexing in many cases.

Take Quiz

Mini Project

Description

Build a small Rust program that prints a numbered shopping list and marks empty entries as invalid. This demonstrates how to use .enumerate() in a realistic task where both the item and its position matter.

Goal

Create a program that loops through a vector, prints each item with a human-friendly number, and reports invalid entries using their index.

Requirements

  • Create a Vec of shopping list items, including at least one empty string.
  • Iterate through the vector using indexed iteration.
  • Print valid items as a numbered list starting from 1.
  • Detect empty items and print an error message with their zero-based index.
Take Quiz

Keep learning

Related questions

View more