Creating Iterators

How to obtain iterators from collections, custom types, ranges, and standard library factories in Rust

Iterators are the gateway between your data and the rich set of transformation and consumption methods Rust provides. Before you can map, filter, or collect, you need an iterator. Rust offers several built‑in ways to create one, and you can teach your own types to produce iterators through the IntoIterator trait. This section walks through every common source of iterators — collection methods, the drain pattern, range expressions, and factory functions — so you can pick the right tool for the job.

How Collection Methods Create Iterators

The standard collections (Vec, HashMap, String, etc.) ship with three conventional methods that hand you an iterator. They are not part of a single trait; instead, each collection implements them directly (or delegates them) to give you control over how you want to access the data.

These methods are iter(), iter_mut(), and into_iter(). Understanding the difference between them is the single most important step in working with iterators.

iter() – Borrowing Each Element

iter() returns an iterator that borrows each element immutably. The collection itself remains untouched and usable afterward.

let numbers = vec![1, 2, 3];
let mut iter = numbers.iter();
assert_eq!(iter.next(), Some(&1));
assert_eq!(iter.next(), Some(&2));
assert_eq!(iter.next(), Some(&3));
assert_eq!(iter.next(), None);
// numbers is still alive and usable
println!("{:?}", numbers); // prints [1, 2, 3]

The items yielded are references (&i32). This is the right call when you only need to read the data, for example counting items, searching for a value, or printing them.

References from iter():

Because you receive &T, methods like map receive a reference. Pay attention to dereference patterns inside closures — |x| *x + 1 versus |&x| x + 1 — both are common and correct, but you must pick one consistently.

iter_mut() – Mutable Borrows for In‑Place Changes

iter_mut() creates an iterator that yields mutable references to each element. You can modify the collection's contents while you iterate, but you cannot add or remove items — the collection's length stays fixed.

let mut values = vec![1, 2, 3];
for v in values.iter_mut() {
    *v *= 2;
}
println!("{:?}", values); // [2, 4, 6]

The original collection remains available after the iteration ends. This method is essential for in‑place transformations like scaling all numbers in a buffer or updating a field in a struct inside a Vec.

Cannot Resize During Iteration:

Rust's borrowing rules forbid holding a mutable reference to a collection and calling methods like push or pop simultaneously. The mutable borrow from iter_mut() blocks structural modifications.

into_iter() – Taking Ownership

into_iter() consumes the collection and yields owned values. After calling it, the original collection is gone — you cannot use it again.

let words = vec!["hello".to_string(), "world".to_string()];
let consumed: Vec<String> = words.into_iter().collect();
// words is no longer accessible here

This is the iterator to use when you want to move data out of the collection, for example sending values to another thread, handing them to a function that expects owned data, or simply destroying the collection and extracting its contents.

into_iter() is not a standalone method — it is the required method of the IntoIterator trait, and every collection implements that trait for itself and for references to itself. That's what allows for loops to work on collections directly.

The Three Forms of Iteration Through for Loops

A for loop in Rust is syntactic sugar for calling into_iter() on the expression you give it. That means for item in expression becomes for item in (expression).into_iter(). What exactly you get depends on what expression is.

  • for item in collection — calls IntoIterator::into_iter() on the owned collection, consuming it and yielding owned items. Equivalent to collection.into_iter().
  • for item in &collection — calls IntoIterator::into_iter() on a shared reference, which yields &Item. Equivalent to collection.iter().
  • for item in &mut collection — calls IntoIterator::into_iter() on a mutable reference, yielding &mut Item. Equivalent to collection.iter_mut().

This design makes iteration feel natural while preserving ownership semantics.

let mut data = vec![10, 20, 30];
// Consuming iteration — moves data
for n in data {
    println!("owned: {}", n);
}
// data is gone now
let mut data2 = vec![10, 20, 30];
// Borrowing iteration — no move
for n in &data2 {
    println!("borrowed: {}", n);
}
println!("still here: {:?}", data2);
// Mutable borrow
for n in &mut data2 {
    *n += 1;
}
println!("after mutate: {:?}", data2);

for x in &vec Does Not Move:

A persistent source of confusion: for x in &vec still yields references, not owned values. If you try to collect those references into a Vec<String>, you'll get a type mismatch. Use for x in vec or vec.into_iter() when you need owned items.

Implementing IntoIterator for Custom Types

Standard collections already implement IntoIterator, but you often want your own types to work with for loops too. The trait requires one method — into_iter(self) — that returns an iterator. The trick is that you can implement it three times: once for the owned type, once for a shared reference, and once for a mutable reference. That gives users of your type the same ergonomic iteration they expect from Vec.

The following example defines a Pixel struct holding three color channels and makes it iterable over its channels.

#[derive(Debug)]
struct Pixel {
    r: u8,
    g: u8,
    b: u8,
}
// --- Consuming iterator ---
struct PixelIntoIter {
    pixel: Pixel,
    index: u8,
}
impl Iterator for PixelIntoIter {
    type Item = u8;
    fn next(&mut self) -> Option<Self::Item> {
        match self.index {
            0 => { self.index = 1; Some(self.pixel.r) }
            1 => { self.index = 2; Some(self.pixel.g) }
            2 => { self.index = 3; Some(self.pixel.b) }
            _ => None,
        }
    }
}
impl IntoIterator for Pixel {
    type Item = u8;
    type IntoIter = PixelIntoIter;
    fn into_iter(self) -> Self::IntoIter {
        PixelIntoIter { pixel: self, index: 0 }
    }
}
// --- Immutable borrowing iterator ---
struct PixelIter<'a> {
    pixel: &'a Pixel,
    index: u8,
}
impl<'a> Iterator for PixelIter<'a> {
    type Item = &'a u8;
    fn next(&mut self) -> Option<Self::Item> {
        match self.index {
            0 => { self.index = 1; Some(&self.pixel.r) }
            1 => { self.index = 2; Some(&self.pixel.g) }
            2 => { self.index = 3; Some(&self.pixel.b) }
            _ => None,
        }
    }
}
impl<'a> IntoIterator for &'a Pixel {
    type Item = &'a u8;
    type IntoIter = PixelIter<'a>;
    fn into_iter(self) -> Self::IntoIter {
        PixelIter { pixel: self, index: 0 }
    }
}

With these implementations, you can use a Pixel in all three iteration styles:

let p = Pixel { r: 255, g: 100, b: 0 };
// Owned iteration (consumes p)
for channel in p {
    println!("{}", channel);
}
let p2 = Pixel { r: 10, g: 20, b: 30 };
// Borrowed iteration — p2 remains usable
for channel in &p2 {
    println!("{}", channel);
}
println!("{:?}", p2); // Ok

The key decision is which iterator type to use. An owned iterator should yield owned values and consume the original data. A borrowing iterator yields references and leaves the source intact.

for Loops Just Work:

If you implement IntoIterator for your type and its references, users can drop your struct into a for loop exactly like a Vec. That integration makes custom collections feel first‑class.

Mutable Iteration Over Struct Fields:

Implementing mutable iteration (&mut self) for a struct with named fields is often more involved. The borrow checker sees the entire struct as mutably borrowed, so you cannot return individual &mut references to fields directly — you typically need unsafe code or a design that stores fields in an array. Many custom types only provide immutable and owning iteration, and that is perfectly fine.

drain: Removing and Iterating Simultaneously

Sometimes you need to pull elements out of a collection without destroying the whole thing. The drain family of methods does exactly that: it takes a mutable reference to the collection, removes a range of elements, and returns an iterator that yields those removed elements as owned values. After draining, the collection still exists — it's just missing the drained items.

Vec<T> provides drain(range), where range can be a Range, RangeFrom, RangeFull, etc.

let mut numbers = vec![10, 20, 30, 40, 50];
let drained: Vec<i32> = numbers.drain(1..4).collect();
println!("drained: {:?}", drained);   // [20, 30, 40]
println!("remaining: {:?}", numbers); // [10, 50]

The collection's capacity is unchanged, but the drained elements are now owned by the returned iterator. You can drain any subset; .. drains everything.

String has drain(range), which yields chars. HashMap and BTreeMap have drain(), which returns an iterator of key‑value pairs, consuming the entire map but leaving the map allocated with its original capacity.

use std::collections::HashMap;
let mut scores = HashMap::from([
    ("Alice", 42),
    ("Bob", 27),
]);
for (name, score) in scores.drain() {
    println!("{}: {}", name, score);
}
assert!(scores.is_empty());
// scores still usable, just empty

drain vs into_iter:

into_iter() consumes the whole collection and gives you an iterator over all elements. drain allows you to remove a slice while keeping the collection alive for reuse. If you need to clear a Vec but keep the allocation, drain(..) is more efficient than into_iter() and re‑allocation.

Iterators from Ranges and Other Language Constructs

Rust provides syntactic forms that directly produce iterators.

Range expressions start..end and start..=end create iterators that yield integers or characters. They are extremely common in for loops and manual iteration.

for i in 0..5 {
    println!("{}", i); // 0,1,2,3,4
}
let letters: Vec<char> = ('a'..='f').collect();
println!("{:?}", letters); // ['a','b','c','d','e','f']

Ranges are lazy and have a known size, so they integrate with all iterator adapters.

Option and Result as iterators. Option<T> implements IntoIterator, yielding either zero or one item. This is handy for flattening optional values in iterator chains.

let maybe = Some(42);
let result: Vec<i32> = maybe.into_iter().collect();
assert_eq!(result, vec![42]);
let none: Option<i32> = None;
let empty: Vec<i32> = none.into_iter().collect();
assert!(empty.is_empty());

Result<T, E> provides iter() and into_iter() as well, yielding a single item on Ok and nothing on Err. That makes it possible to process fallible operations inside iterator pipelines.

Iterator Factories in std::iter

The std::iter module offers several functions that produce iterators from scratch. These are useful when you need a stream of values without a backing collection.

  • std::iter::once(value) – yields the value exactly once.
  • std::iter::repeat(value) – yields the same value forever (until you .take(n)).
  • std::iter::empty() – yields nothing; useful as a placeholder for “no items”.
  • std::iter::from_fn(closure) – repeatedly calls the closure to produce items. The closure returns Option<T> to signal when to stop.
  • std::iter::successors(first, closure) – generates a chain where each element is computed from the previous one, until the closure returns None.
use std::iter;
let ones = iter::repeat(1).take(5).collect::<Vec<_>>();
println!("{:?}", ones); // [1,1,1,1,1]
let even_numbers = iter::from_fn(|| {
    static mut N: i32 = 0;
    unsafe {
        N += 2;
        if N <= 10 { Some(N) } else { None }
    }
}).collect::<Vec<_>>();
println!("{:?}", even_numbers); // [2,4,6,8,10]

These factories compose nicely with adapter methods to model complex sequences without allocating intermediate collections.

Laziness Applies Here Too:

Iterator factories like from_fn and successors are lazy. The closure won't run until you consume the iterator (e.g., with collect, for, or next). This means side‑effect‑heavy closures are delayed — be intentional about when you trigger consumption.

Summary

Creating an iterator is about choosing the right ownership model and source. You have four main avenues:

  • Collection methods (iter, iter_mut, into_iter) give you borrowing or ownership directly from standard types.
  • IntoIterator implementations allow your own types to work in for loops and iterator chains, with separate control over owned, shared, and mutable iteration.
  • drain offers a middle ground: extract owned elements from a live collection without destroying it.
  • Range syntax and factory functions produce iterators from thin air when you don't need a collection at all.

Once you have an iterator, the entire arsenal of adapters (map, filter, fold…) and consumers (collect, sum, find…) is available.