| |

Rust 53 🦀 Processing Collections with Iterators

⏱️ Estimated read: 17 min | |

The previous chapter established the iterator trait and the split between adapters and consumers. This chapter applies that machinery to real collections. Vec, HashMap, HashSet, BTreeMap, String, and slices all implement IntoIterator, and each one offers a different set of iteration forms. Knowing which iterator each collection produces, what type its items are, and how to move between collections inside a single chain is what turns a sequence of awkward loops into a single readable pipeline. This chapter is a working reference for the practical question: given a collection, which iterator do I reach for?

Key point: Every collection implements IntoIterator in three forms. iter() borrows shared, iter_mut() borrows mutable, and into_iter() consumes. The item type changes with each form, and choosing the wrong one is the most common source of borrow-checker frustration when working with collections.


Why collection iteration deserves its own treatment

The item-type problem. A Vec<T> yields &T, &mut T, or T depending on which form is used. A HashMap<K, V> yields (&K, &V), (&K, &mut V), or (K, V). A String yields char, &str, or u8 depending on the method. The item type determines what the closure receives and what the chain can do. Getting it right is the first step in every iteration task.

The consumption problem. into_iter() consumes the collection. After it runs, the collection is gone. This is often desirable — it avoids cloning — but it changes the ownership picture of the surrounding code. Choosing iter() preserves the collection at the cost of references.

The ordering problem. Vec iterates in insertion order. HashMap iterates in an unspecified order. BTreeMap iterates in key order. HashSet iterates in an unspecified order. Programs that rely on HashMap iteration order are incorrect, even if they appear to work.

The mutation problem. Mutating a collection while iterating it is a borrow-checker error. The mutable iterator holds a mutable borrow of the collection, and any other access is rejected. Understanding this rule is what lets a developer restructure a loop that “just needs to update one field” into something the compiler accepts.

The conversion problem. Many tasks move between collection types inside a single chain: a Vec becomes a HashMap, a HashMap becomes a Vec of tuples, a String becomes a Vec of words. The collect consumer handles the conversion, but the target type must implement FromIterator.

The efficiency problem. Collecting intermediate results is sometimes necessary, but often avoidable. A chain that calls collect twice when once would do is wasting allocations. The lazy adapter model lets the whole chain run in one pass if the target type allows it.


a. Vec and slices

Vec<T> is the most common collection. It is ordered, contiguous, and supports all three iteration forms.

The three forms

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

    // Shared borrow: items are &i32.
    for x in v.iter() { }

    // Mutable borrow: items are &mut i32.
    for x in v.iter_mut() { *x += 1; }

    // Consuming: items are i32, v is gone.
    for x in v.into_iter() { }
}

The for x in &v form is shorthand for v.iter(), and for x in &mut v is shorthand for v.iter_mut(). Both are idiomatic and produce the same iterators.

Indexed iteration

enumerate pairs each item with its index.

let v = vec!["a", "b", "c"];
for (i, s) in v.iter().enumerate() {
    println!("{}: {}", i, s);
}

The index is a usize starting at zero. To start at a different number, add to the index in the loop body.

Windows and chunks

Slices provide windows(n) and chunks(n) for iterating over sub-slices.

let data = [1, 2, 3, 4, 5];

for w in data.windows(2) {
    println!("{:?}", w);  // [1,2], [2,3], [3,4], [4,5]
}

for c in data.chunks(2) {
    println!("{:?}", c);  // [1,2], [3,4], [5]
}

windows yields overlapping sub-slices of fixed length. chunks yields non-overlapping sub-slices, with the last chunk possibly shorter.

Chunks_exact and rchunks

chunks_exact(n) yields only full-length chunks and drops the remainder. rchunks(n) iterates from the end.

let data = [1, 2, 3, 4, 5];
for c in data.chunks_exact(2) {
    println!("{:?}", c);  // [1,2], [3,4]
}

The Vec adapter chain

The standard pattern is to filter, transform, and collect.

let v = vec![1, 2, 3, 4, 5, 6];
let evens_doubled: Vec<i32> = v.iter()
    .filter(|&&x| x % 2 == 0)
    .map(|&x| x * 2)
    .collect();
assert_eq!(evens_doubled, vec![4, 8, 12]);

The &&x pattern in the filter is because iter() yields &i32, and filter passes &&i32 to the predicate. The &x in the map dereferences the &i32 to i32.

Consuming a Vec

When the Vec is no longer needed, into_iter avoids cloning.

let names = vec![String::from("alice"), String::from("bob")];
let uppercase: Vec<String> = names.into_iter()
    .map(|s| s.to_uppercase())
    .collect();
// names is no longer available.

Each String is moved into the closure, transformed, and moved into the new Vec.


b. HashMap and HashSet

HashMap<K, V> iterates as pairs of (&K, &V). HashSet<T> iterates as &T. Neither guarantees order.

The iteration forms

use std::collections::HashMap;

fn main() {
    let mut map = HashMap::new();
    map.insert("a", 1);
    map.insert("b", 2);

    // Shared borrow: items are (&K, &V).
    for (k, v) in &map { }

    // Mutable borrow: items are (&K, &mut V).
    for (k, v) in &mut map { *v += 1; }

    // Consuming: items are (K, V).
    for (k, v) in map { }
}

The shared form yields a tuple of references. The mutable form allows changing the value but not the key, because the key is part of the hash and changing it would corrupt the table.

The keys, values, and values_mut methods

HashMap provides keys(), values(), and values_mut() for iterating over just one side.

for key in map.keys() { }
for value in map.values() { }
for value in map.values_mut() { *value += 1; }

keys() yields &K, values() yields &V, and values_mut() yields &mut V.

Building a HashMap from a Vec

A common pattern is to build a map from a collection of pairs.

let pairs = vec![("a", 1), ("b", 2), ("c", 3)];
let map: HashMap<&str, i32> = pairs.into_iter().collect();

The collect consumer builds the map because HashMap implements FromIterator<(K, V)>. Duplicate keys keep the last value.

Transforming a HashMap

A map can be transformed into another map by iterating and collecting.

let doubled: HashMap<&str, i32> = map.iter()
    .map(|(k, v)| (*k, v * 2))
    .collect();

Each (k, v) is a tuple of references. The *k copies the key and v * 2 computes the new value.

HashSet operations

HashSet yields &T. The collect consumer builds a set from any iterator.

use std::collections::HashSet;

let set: HashSet<i32> = vec![1, 1, 2, 3, 3].into_iter().collect();
assert_eq!(set.len(), 3);

Set operations

HashSet provides union, intersection, difference, and symmetric_difference.

let a: HashSet<i32> = vec![1, 2, 3].into_iter().collect();
let b: HashSet<i32> = vec![3, 4, 5].into_iter().collect();

let union: HashSet<i32> = a.union(&b).copied().collect();
let intersection: HashSet<i32> = a.intersection(&b).copied().collect();

The set operations return iterators of references, so copied() converts &i32 to i32 before collecting.

The unordered caveat

HashMap and HashSet iterate in an unspecified order. If order matters, use BTreeMap or BTreeSet, which iterate in key order.


c. BTreeMap, BTreeSet, and String

BTreeMap and BTreeSet are sorted collections. String and &str have their own iteration forms for characters, bytes, and lines.

BTreeMap and BTreeSet

The iteration forms match HashMap and HashSet, but the order is sorted by key.

use std::collections::BTreeMap;

let mut map = BTreeMap::new();
map.insert("c", 3);
map.insert("a", 1);
map.insert("b", 2);

for (k, v) in &map {
    println!("{}: {}", k, v);  // a: 1, b: 2, c: 3
}

The sorted order makes BTreeMap appropriate for range queries. The range method iterates over a key range.

for (k, v) in map.range("a".."c") {
    println!("{}: {}", k, v);  // a: 1, b: 2
}

String characters

String and &str provide chars() for Unicode scalar values and bytes() for UTF-8 bytes.

let s = "hello";
for c in s.chars() { }
for b in s.bytes() { }

chars() yields char, which is a Unicode scalar value. bytes() yields u8. For ASCII strings, they are equivalent, but for non-ASCII they differ.

String lines and words

lines() splits on newlines. split_whitespace() splits on any whitespace.

let text = "first line\nsecond line\nthird line";
for line in text.lines() { }

let sentence = "hello world foo";
for word in sentence.split_whitespace() { }

lines() yields &str for each line without the trailing newline. split_whitespace() yields &str for each word.

The split method

split(pat) splits on a pattern. The pattern can be a character, a string, or a closure.

let csv = "a,b,c";
for field in csv.split(',') { }

let multi = "a  b   c";
for field in multi.split(' ') { }  // Yields empty strings.

Building a String from an iterator

collect can build a String from an iterator of char or &str.

let chars: Vec<char> = vec!['h', 'i'];
let s: String = chars.into_iter().collect();

let words = vec!["hello", " ", "world"];
let sentence: String = words.into_iter().collect();

String implements FromIterator<char> and FromIterator<&str>.

The join method

join concatenates string slices with a separator.

let words = vec!["hello", "world"];
let sentence = words.join(" ");
assert_eq!(sentence, "hello world");

join is a slice method, not an iterator method, but it is the idiomatic way to combine an iterator of strings.


Complete Example Session

// ============================================
// PART 1: VEC — THE THREE FORMS
// ============================================

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

    for x in &v { println!("{}", x); }       // &i32
    for x in &mut v { *x *= 2; }             // &mut i32
    let owned: Vec<i32> = v.into_iter().collect();  // i32
}

// ============================================
// PART 2: VEC — FILTER AND MAP
// ============================================

fn main() {
    let v = vec![1, 2, 3, 4, 5, 6];
    let result: Vec<i32> = v.iter()
        .filter(|&&x| x % 2 == 0)
        .map(|&x| x * 2)
        .collect();
    assert_eq!(result, vec![4, 8, 12]);
}

// ============================================
// PART 3: VEC — WINDOWS AND CHUNKS
// ============================================

fn main() {
    let data = [1, 2, 3, 4, 5];
    let pairs: Vec<_> = data.windows(2).collect();
    assert_eq!(pairs, vec![&[1, 2][..], &[2, 3], &[3, 4], &[4, 5]]);

    let groups: Vec<_> = data.chunks(2).collect();
    assert_eq!(groups.len(), 3);  // [1,2], [3,4], [5]
}

// ============================================
// PART 4: HASHMAP — ITERATION
// ============================================

use std::collections::HashMap;

fn main() {
    let mut scores = HashMap::new();
    scores.insert("alice", 10);
    scores.insert("bob", 20);

    for (name, score) in &scores {
        println!("{}: {}", name, score);
    }

    let total: i32 = scores.values().sum();
    assert_eq!(total, 30);
}

// ============================================
// PART 5: HASHMAP — BUILDING FROM A VEC
// ============================================

fn main() {
    let pairs = vec![("a", 1), ("b", 2), ("c", 3)];
    let map: HashMap<&str, i32> = pairs.into_iter().collect();
    assert_eq!(map["a"], 1);
}

// ============================================
// PART 6: HASHSET — DEDUPLICATION
// ============================================

use std::collections::HashSet;

fn main() {
    let numbers = vec![1, 1, 2, 3, 3, 3];
    let unique: HashSet<i32> = numbers.into_iter().collect();
    assert_eq!(unique.len(), 3);
}

// ============================================
// PART 7: BTREEMAP — SORTED ITERATION
// ============================================

use std::collections::BTreeMap;

fn main() {
    let mut map = BTreeMap::new();
    map.insert("c", 3);
    map.insert("a", 1);
    map.insert("b", 2);

    let keys: Vec<&&str> = map.keys().collect();
    assert_eq!(keys, vec![&"a", &"b", &"c"]);
}

// ============================================
// PART 8: STRING — CHARS, LINES, WORDS
// ============================================

fn main() {
    let s = "hello world";
    let chars: Vec<char> = s.chars().collect();
    assert_eq!(chars.len(), 11);

    let words: Vec<&str> = s.split_whitespace().collect();
    assert_eq!(words, vec!["hello", "world"]);
}

// ============================================
// PART 9: STRING — JOIN AND COLLECT
// ============================================

fn main() {
    let words = vec!["hello", "world"];
    let sentence = words.join(" ");
    assert_eq!(sentence, "hello world");

    let chars: Vec<char> = vec!['h', 'i'];
    let s: String = chars.into_iter().collect();
    assert_eq!(s, "hi");
}

// ============================================
// PART 10: MOVING BETWEEN COLLECTIONS
// ============================================

use std::collections::BTreeMap;

fn main() {
    // Vec of tuples → HashMap.
    let pairs = vec![("a", 1), ("b", 2)];
    let map: HashMap<&str, i32> = pairs.into_iter().collect();

    // HashMap → Vec of tuples, sorted.
    let mut sorted: Vec<(&str, i32)> = map.into_iter().collect();
    sorted.sort_by_key(|&(k, _)| k);

    // Vec of tuples → BTreeMap.
    let btree: BTreeMap<&str, i32> = sorted.into_iter().collect();

    // BTreeMap → Vec of keys.
    let keys: Vec<&str> = btree.keys().copied().collect();
    assert_eq!(keys, vec!["a", "b"]);
}

The ten parts progressed from the Vec iteration forms through filter and map, windows and chunks, HashMap iteration and construction, HashSet deduplication, BTreeMap sorted iteration, String characters and words, join and collect, and moving between collection types. The same collections ran through every part, showing how each type’s iterator is used.


Quick Reference

Iteration Forms by Collection

Collectioniter() yieldsiter_mut() yieldsinto_iter() yields
Vec<T>&T&mut TT
HashMap<K, V>(&K, &V)(&K, &mut V)(K, V)
HashSet<T>&T—T
BTreeMap<K, V>(&K, &V)(&K, &mut V)(K, V)
BTreeSet<T>&T—T
String / &strchar via chars(), u8 via bytes()——

Slice Methods

MethodYieldsPurpose
windows(n)Sub-slices of length nOverlapping windows
chunks(n)Sub-slices of length up to nNon-overlapping chunks
chunks_exact(n)Sub-slices of length nDrops remainder
rchunks(n)Sub-slices from the endReverse chunks

HashMap and BTreeMap Methods

MethodYieldsOrder
keys()&KHash: unspecified; BTree: sorted
values()&VSame
values_mut()&mut VSame
iter()(&K, &V)Same
range(..)(&K, &V)BTree only; sorted

String Methods

MethodYieldsSplits On
chars()charUnicode scalar values
bytes()u8UTF-8 bytes
lines()&strNewlines
split_whitespace()&strAny whitespace
split(pat)&strThe pattern

Collection Conversions

FromToMethod
Vec<(K, V)>HashMap<K, V>collect()
Vec<T>HashSet<T>collect()
HashMap<K, V>Vec<(K, V)>collect()
Vec<&str>Stringjoin("")
Vec<char>Stringcollect()

Best Practices

✅ Do This:

// Choose the iteration form that matches the need.
for x in &v { }           // ✅ Shared borrow
for x in &mut v { }       // ✅ Mutable borrow
for x in v { }            // ✅ Consume when the collection is done

// Use into_iter to avoid cloning.
let upper: Vec<String> = names.into_iter().map(|s| s.to_uppercase()).collect();  // ✅

// Use enumerate instead of a manual counter.
for (i, x) in v.iter().enumerate() { }  // ✅

// Use values() when only values are needed.
let total: i32 = scores.values().sum();  // ✅

// Use BTreeMap when sorted iteration is required.
let sorted: BTreeMap<_, _> = map.into_iter().collect();  // ✅

// Use split_whitespace for word splitting.
for word in text.split_whitespace() { }  // ✅

❌ Don’t Do This:

// Don't clone the collection just to iterate.
for x in v.clone() { }  // ❌ Use &v or into_iter

// Don't rely on HashMap iteration order.
let first = map.iter().next();  // ❌ Order is unspecified

// Don't collect intermediate results unnecessarily.
let temp: Vec<_> = v.iter().map(...).collect();
let result: Vec<_> = temp.into_iter().filter(...).collect();  // ❌ One pass suffices

// Don't use split(' ') when split_whitespace is meant.
"a  b".split(' ')  // ❌ Yields empty strings

// Don't ignore the remainder with chunks_exact.
data.chunks_exact(3)  // ✅ If remainder is intentionally dropped

// Don't mutate while iterating without iter_mut.
for x in &v { v.push(...); }  // ❌ Borrow error

Common Pitfalls

PitfallWhy It HappensFix
&&x in filteriter() yields &T, filter passes &&TPattern-match the double reference
Cannot move out of &TShared borrow yields referencesUse into_iter() or clone
Unsorted HashMap outputHash order is unspecifiedUse BTreeMap
split yields empty stringsConsecutive separatorsUse split_whitespace
Borrow error in loopMutating while iteratingUse iter_mut or restructure
Collect type ambiguousTarget type not specifiedAnnotate the binding
Intermediate Vec allocatedTwo collect callsCombine into one chain
Keys change in iter_mutKey is part of hashMutate values only

Real-World Examples

1. Filter and Double

let doubled: Vec<i32> = v.iter().filter(|&&x| x > 0).map(|&x| x * 2).collect();

2. Sum Values

let total: i32 = scores.values().sum();

3. Build a Map

let map: HashMap<&str, i32> = pairs.into_iter().collect();

4. Deduplicate

let unique: HashSet<i32> = numbers.into_iter().collect();

5. Sorted Keys

let keys: Vec<&&str> = btree_map.keys().collect();

6. Split Words

let words: Vec<&str> = text.split_whitespace().collect();

7. Join Strings

let sentence = words.join(" ");

8. Sliding Window

for pair in data.windows(2) { }

9. Batch Processing

for batch in data.chunks(100) { }

10. Convert HashMap to Sorted Vec

let mut sorted: Vec<_> = map.into_iter().collect();
sorted.sort_by_key(|&(k, _)| k);

Visual

Iteration Forms

┌────────────────────────────────────────────────────────────┐
│  THREE FORMS OF COLLECTION ITERATION                       │
│                                                            │
│  Collection: Vec<T>                                        │
│  ┌──────────────────────────────────────────────────┐     │
│  │  &v / v.iter()        →  &T (shared borrow)       │     │
│  │  &mut v / v.iter_mut() →  &mut T (mutable borrow) │     │
│  │  v / v.into_iter()    →  T (owned, consumes)      │     │
│  └──────────────────────────────────────────────────┘     │
│                                                            │
│  Collection: HashMap<K, V>                                 │
│  ┌──────────────────────────────────────────────────┐     │
│  │  &map / map.iter()     →  (&K, &V)                │     │
│  │  &mut map              →  (&K, &mut V)            │     │
│  │  map / map.into_iter() →  (K, V)                  │     │
│  └──────────────────────────────────────────────────┘     │
│                                                            │
│  The choice determines what the closures see.              │
│  into_iter avoids clones but consumes the collection.      │
│                                                            │
└────────────────────────────────────────────────────────────┘

Collection Conversions

┌────────────────────────────────────────────────────────────┐
│  MOVING BETWEEN COLLECTION TYPES VIA collect()             │
│                                                            │
│  Vec<(K, V)> ──────────────▶ HashMap<K, V>                 │
│       │                       ▲                            │
│       │  .into_iter().collect()                            │
│       │                                                    │
│       ▼                                                    │
│  HashMap<K, V> ────────────▶ Vec<(K, V)>                   │
│       │                       ▲                            │
│       │  .into_iter().collect()                            │
│       │                                                    │
│       ▼                                                    │
│  BTreeMap<K, V> ──────────▶ Vec<&K> (keys)                 │
│                                                            │
│  collect targets any type implementing FromIterator.       │
│  HashMap: FromIterator<(K, V)>                             │
│  HashSet: FromIterator<T>                                  │
│  Vec: FromIterator<T>                                      │
│  String: FromIterator<char> or FromIterator<&str>          │
│                                                            │
└────────────────────────────────────────────────────────────┘

String Iteration

┌────────────────────────────────────────────────────────────┐
│  ITERATING A STRING                                        │
│                                                            │
│  let s = "hello\nworld";                                   │
│                                                            │
│  s.chars()  →  'h', 'e', 'l', 'l', 'o', '\n', 'w', ...    │
│    Unicode scalar values, one per code point.              │
│                                                            │
│  s.bytes()  →  104, 101, 108, 108, 111, 10, 119, ...       │
│    UTF-8 bytes. Equivalent to chars for ASCII only.        │
│                                                            │
│  s.lines()  →  "hello", "world"                            │
│    Splits on newlines, strips the newline.                 │
│                                                            │
│  s.split_whitespace()  →  "hello", "world"                 │
│    Splits on any whitespace, no empty strings.             │
│                                                            │
│  s.split(',')  →  splits on the literal comma              │
│    May yield empty strings between consecutive commas.     │
│                                                            │
└────────────────────────────────────────────────────────────┘

Ordered vs Unordered

┌────────────────────────────────────────────────────────────┐
│  ITERATION ORDER BY COLLECTION                             │
│                                                            │
│  Vec<T>                                                    │
│    Insertion order. Predictable.                           │
│    [3, 1, 2] iterates as 3, 1, 2.                          │
│                                                            │
│  HashMap<K, V>                                             │
│    Unspecified. Depends on hash function and capacity.     │
│    Not safe to rely on.                                    │
│                                                            │
│  HashSet<T>                                                │
│    Unspecified. Same caveat as HashMap.                    │
│                                                            │
│  BTreeMap<K, V>                                            │
│    Sorted by key. Predictable.                             │
│    Insertion order does not matter.                        │
│                                                            │
│  BTreeSet<T>                                               │
│    Sorted. Predictable.                                    │
│                                                            │
│  Use BTree variants when order matters.                    │
│                                                            │
└────────────────────────────────────────────────────────────┘

Summary

ItemValue
iter()Yields &T; shared borrow
iter_mut()Yields &mut T; mutable borrow
into_iter()Yields T; consumes the collection
windows(n)Overlapping sub-slices
chunks(n)Non-overlapping sub-slices
chars()Unicode scalar values
bytes()UTF-8 bytes
lines()Lines without newlines
split_whitespace()Words without empty strings
keys() / values()One side of a map
values_mut()Mutable values
range(..)Key range in BTreeMap
join(sep)Concatenate strings with a separator
collect()Build any FromIterator target

Key takeaways:

  • Every collection has three iteration forms. iter() borrows shared, iter_mut() borrows mutable, and into_iter() consumes. The item type changes with each form, and choosing the wrong one is the most common source of borrow-checker frustration.
  • HashMap and HashSet iterate in unspecified order. Programs that rely on hash iteration order are incorrect. Use BTreeMap or BTreeSet when order matters.
  • collect builds any FromIterator type. A Vec of tuples becomes a HashMap, a Vec becomes a HashSet, a HashMap becomes a Vec of tuples. The target type is inferred from context.
  • Slices provide windows and chunks for sub-slice iteration. windows overlaps, chunks does not. chunks_exact drops the remainder, and rchunks iterates from the end.
  • Strings have several iteration methods. chars for Unicode scalar values, bytes for UTF-8, lines for line splitting, split_whitespace for words. split with a character may yield empty strings between consecutive separators.
  • join is the idiomatic way to combine an iterator of strings. A slice method, not an iterator method, but it complements the iterator API.
  • Mutating a collection while iterating requires iter_mut. A shared borrow and a mutable access cannot coexist. The compiler rejects the combination, and the fix is to use iter_mut or restructure the loop.
  • Intermediate collect calls are often avoidable. A single chain that filters, transforms, and collects into the final type is more efficient than multiple collects. The lazy adapter model ensures the work happens once.

Remember: Processing collections with iterators is a matter of choosing the right iteration form and chaining adapters into a consumer. Vec yields references or owned values depending on the form. HashMap and HashSet iterate unordered; BTreeMap and BTreeSet iterate sorted. String provides chars, bytes, lines, and split_whitespace for different notions of iteration. collect converts between collection types wherever the target implements FromIterator. The whole set of patterns is built on the same primitives: iter, iter_mut, into_iter, adapters, and consumers. Once the item type of each form is known, the chain writes itself.


Was this helpful?


Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!