Nugget 19

Home

The Three Vec Iterators

v.iter(), v.iter_mut(), v.into_iter() — three methods, three ownership stories. Picking the right one is the first decision in every iterator pipeline.

The Core Decision

You cannot call v.map(...) directly on a Vec. You must first pick one of three iterator methods. Each answers a different question:

// 1. iter() — "I just want to look at the values"
let v = vec![1, 2, 3];
let doubled: Vec<i32> = v.iter().map(|x| x * 2).collect();
println!("{:?}", v); // ✅ v is still usable

// 2. iter_mut() — "I want to change the values in place"
let mut v = vec![1, 2, 3];
v.iter_mut().for_each(|x| *x *= 2);
println!("{:?}", v); // ✅ [2, 4, 6]

// 3. into_iter() — "I'm done with this Vec, give me owned values"
let v = vec![1, 2, 3];
let doubled: Vec<i32> = v.into_iter().map(|x| x * 2).collect();
// println!("{:?}", v); // ❌ v was consumed

Quick Reference

Method Yields Ownership Use when
.iter() &T Borrows (read) Reading values, keep the Vec
.iter_mut() &mut T Mutable borrow Modifying elements in place
.into_iter() T Consumes (move) Transforming to a new collection

.iter() — Read-Only Access

The default choice. Borrows each element immutably — the original Vec stays available after the pipeline:

let numbers = vec![1, 2, 3, 4];

// Sum — doesn't need ownership
let sum: i32 = numbers.iter().sum();

// Filter — keeps references
let evens: Vec<&i32> = numbers.iter()
    .filter(|x| *x % 2 == 0)
    .collect();

// Map — each x is &i32
let doubled: Vec<i32> = numbers.iter()
    .map(|x| x * 2)  // x: &i32, but i32 is Copy so * happens auto
    .collect();

println!("numbers is still here: {:?}", numbers); // ✅

💡 The for loop shortcut

for item in &v is equivalent to v.iter(). The & before v triggers the IntoIterator impl for &Vec, which returns the same iterator.

.iter_mut() — Modify in Place

Each item is a mutable reference (&mut T). You can change the elements without creating a new Vec:

let mut scores = vec![10, 20, 30];

// Add a bonus to every score
scores.iter_mut().for_each(|x| *x += 5);
println!("{:?}", scores);  // [15, 25, 35]

// Double only evens using mutable map-like pattern
let mut nums = vec![1, 2, 3, 4];
nums.iter_mut().for_each(|x| {
    if *x % 2 == 0 {
        *x *= 10;
    }
});
println!("{:?}", nums);  // [1, 20, 3, 40]

// The Vec is still usable after — borrow has ended
println!("Original vector: {:?}", nums); // ✅

📐 iter_mut() + map

map with iter_mut() is awkward because map wants to return values. for_each is usually the better fit for in-place mutation — it exists just for side effects.

.into_iter() — Consume and Transform

Takes ownership of each element. The original Vec is consumed — you can't use it after. This is perfect for one-shot transformations:

let strings = vec!["hello", "world"];

// Transform into owned Strings
let uppercased: Vec<String> = strings
    .into_iter()
    .map(|s| s.to_uppercase())
    .collect();

println!("{:?}", uppercased);  // ["HELLO", "WORLD"]
// println!("{:?}", strings);  // ❌ consumed by into_iter()

let numbers = vec![1, 2, 3];

// into_iter yields T directly — no references to dereference
let doubled: Vec<i32> = numbers
    .into_iter()
    .map(|x| x * 2)     // x is i32, clean and simple
    .collect();

// Also the simplest for filter:
let v = vec![1, 2, 3];
let evens: Vec<i32> = v.into_iter()
    .filter(|&x| x % 2 == 0)  // filter receives &T, so |&x|
    .collect();

💡 for loop equivalent

for item in v (no &) is equivalent to v.into_iter() — it consumes the Vec. If you want to keep it, write for item in &v instead.

Same Task, Three Approaches

Here's "double every element" done three ways depending on what you need afterward:

let numbers = vec![1, 2, 3];

// Keep original + produce new Vec (most common)
let doubled: Vec<i32> = numbers.iter().map(|x| x * 2).collect();
// numbers still usable ✅

let mut numbers = vec![1, 2, 3];

// Modify in place + keep the same Vec
numbers.iter_mut().for_each(|x| *x *= 2);
// numbers changed to [2, 4, 6] ✅

let numbers = vec![1, 2, 3];

// Consume original + produce new Vec
let doubled: Vec<i32> = numbers.into_iter().map(|x| x * 2).collect();
// numbers gone — but no references to clean up ❌

iter() is the safest default. Drop down to into_iter() when the ownership semantics are what you want (no dangling references, clean move). Reach for iter_mut() only when you specifically want to mutate in place.

📐 Why pros prefer the explicit methods

for item in &v works for simple reading, but the moment you reach for map, filter, or collect, the for loop fights you — you restructure into multiple statements. Explicit iterators compose naturally: v.iter().map(...).filter(...).collect() in one expression. They also make ownership visible at a glance — .iter() says "borrowing", .into_iter() says "consuming" — right there in the method name. Seasoned Rustaceans reach for them by habit, not because for is wrong, but because iterators are more expressive once you're comfortable with the model.

How Closures Change

Because each method yields a different type, the closure patterns change (connecting to Nugget 18):

let v = vec![1, 2, 3];

// iter() yields &i32 → map gets &i32, filter gets &&i32
v.iter().map(|x| x * 2);         // x: &i32
v.iter().filter(|&x| x > 1);     // x: &i32 (destructured &)

// iter_mut() yields &mut i32 → for_each gets &mut i32
v.iter_mut().for_each(|x| *x *= 2);  // x: &mut i32, need *x

// into_iter() yields i32 → map gets i32, filter gets &i32
v.into_iter().map(|x| x * 2);        // x: i32 (clean!)
// For filter, same rule as always: filter adds one &
// v.into_iter().filter(|&x| x > 1); // x: i32

Key Takeaways