What is the difference between Iterator and IntoIterator

Iterator produces items one by one, while IntoIterator converts collections into iterators.

The loop that eats your data

You write a function to process a list of numbers. You test it with a Vec. It works. You try to pass a slice from a file buffer, and the compiler complains. You try to pass an existing iterator from a filter chain, and it complains again. You end up writing three versions of the same function.

Rust has a way to accept all three with one signature. The key is understanding the split between producing items and turning things into producers. Iterator defines how to produce a sequence. IntoIterator defines how to convert a collection into that sequence. The for loop bridges the gap automatically, but knowing the difference prevents accidental data loss and unlocks flexible API design.

Iterator is the stream, IntoIterator is the adapter

Iterator is a trait for anything that can yield items one by one. It has one required method: next(). Each call returns Option<T>. Some(item) gives you the next value. None means the stream is exhausted. The iterator holds the state of where you are in the sequence.

IntoIterator is a trait for collections and containers. It has one required method: into_iter(). This method consumes the collection and returns a type that implements Iterator.

Think of a sealed water bottle. The bottle holds water, but you can't drink from it directly. IntoIterator is the mechanism that attaches a tap to the bottle. Once the tap is attached, you have an Iterator. You turn the handle, water flows. The bottle is now open and being consumed. You can't reseal it.

The for loop is the consumer. When you write for x in collection, Rust desugars this into a call to into_iter() followed by a loop calling next(). You rarely call into_iter() manually because the loop does it for you. The magic happens behind the scenes.

Minimal example

use std::iter::{Iterator, IntoIterator};

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

    // into_iter() consumes the vector and returns an iterator.
    // The vector is moved here; `v` is no longer accessible.
    let mut iter: impl Iterator<Item = i32> = v.into_iter();

    // next() pulls the next item or returns None when exhausted.
    // This is the core method every Iterator must implement.
    if let Some(first) = iter.next() {
        println!("First: {}", first);
    }

    // The iterator remembers its position.
    // Calling next() again advances the stream.
    println!("Second: {:?}", iter.next()); // Some(2)
    println!("Third: {:?}", iter.next());  // Some(3)
    println!("Done: {:?}", iter.next());   // None
}

The type annotation impl Iterator<Item = i32> tells the compiler we expect an iterator that yields i32. The actual type returned by Vec::into_iter() is std::vec::IntoIter<i32>, but you usually don't need to name it. Rust infers the concrete type.

How the for loop works

The for loop is syntax sugar. It hides the conversion and the loop logic. When you write:

for item in collection {
    println!("{}", item);
}

The compiler rewrites this roughly as:

let mut iter = collection.into_iter();
while let Some(item) = iter.next() {
    println!("{}", item);
}

This desugaring explains why for loops work on vectors, slices, and ranges. Each of these types implements IntoIterator. The loop calls into_iter() to get the stream, then drives the stream with next() until it returns None.

If you try to call next() directly on a vector, the compiler rejects you with E0599 (no method named next found for struct Vec). Vectors don't implement Iterator. They implement IntoIterator. You must convert the vector first.

Realistic example: custom collection

Implementing IntoIterator for a custom type lets users write for loops over your data. You need two pieces: a struct that implements Iterator to hold the iteration state, and an implementation of IntoIterator that returns that struct.

struct MyVec {
    data: Vec<i32>,
}

// The iterator struct holds the state of the iteration.
// Convention: name it XxxIter to match the collection name.
struct MyVecIter {
    data: Vec<i32>,
    index: usize,
}

impl Iterator for MyVecIter {
    type Item = i32;

    // next() must advance the state and return the next item.
    // Return None when the index reaches the end.
    fn next(&mut self) -> Option<Self::Item> {
        if self.index < self.data.len() {
            let item = self.data[self.index];
            self.index += 1;
            Some(item)
        } else {
            None
        }
    }
}

impl IntoIterator for MyVec {
    type Item = i32;
    type IntoIter = MyVecIter;

    // into_iter() consumes self and returns the iterator.
    // This moves the data into the iterator struct.
    fn into_iter(self) -> Self::IntoIter {
        MyVecIter {
            data: self.data,
            index: 0,
        }
    }
}

fn main() {
    let my_vec = MyVec {
        data: vec![10, 20, 30],
    };

    // The for loop calls into_iter() automatically.
    // Users can iterate over MyVec just like a Vec.
    for value in my_vec {
        println!("{}", value);
    }
}

The associated types Item and IntoIter tell the compiler what comes out and what you get back. Item is the type yielded by next(). IntoIter is the concrete type returned by into_iter(). You must implement both traits together. IntoIterator depends on Iterator.

Convention aside: name your iterator struct XxxIter. The community expects this pattern. It makes the type name self-documenting. Also, keep the iterator struct private if possible. Expose only the IntoIterator implementation. Users don't need to construct the iterator manually.

Flexible function arguments

The real power of IntoIterator appears in function signatures. You can write a function that accepts anything convertible to an iterator. This accepts Vec, slices, existing iterators, and tuples without forcing the caller to convert.

// Accepts anything that can become an iterator.
// Binding Item = i32 prevents type ambiguity.
fn sum_all<I: IntoIterator<Item = i32>>(source: I) -> i32 {
    let mut total = 0;
    // The compiler inserts .into_iter() here automatically.
    for item in source {
        total += item;
    }
    total
}

fn main() {
    let v = vec![1, 2, 3];
    // Passes the vector by value.
    println!("{}", sum_all(v));

    let s = &[4, 5, 6];
    // Passes a slice. Slices also implement IntoIterator.
    println!("{}", sum_all(s));

    let iter = [7, 8, 9].into_iter();
    // Passes an existing iterator.
    println!("{}", sum_all(iter));
}

Convention aside: prefer impl IntoIterator in argument position for readability. fn sum_all(source: impl IntoIterator<Item = i32>) is cleaner than the generic syntax. Both compile to the same code. The impl Trait form is the community standard for function arguments.

Always bind the Item type. If you write fn process<I: IntoIterator>(items: I), the compiler cannot infer what type the iterator yields. Callers can pass a Vec<i32> or a Vec<String>, and the function body won't know what to do. Binding Item constrains the input and gives the compiler the information it needs.

Pitfalls and compiler errors

Consuming vs borrowing

into_iter() consumes the collection. The collection is moved into the iterator. You cannot use the collection afterward.

let v = vec![1, 2, 3];
let iter = v.into_iter();
println!("{:?}", v); // Error

The compiler rejects this with E0382 (use of moved value). The vector was moved into the iterator. If you need the data after iterating, use .iter() instead. .iter() borrows the collection and yields references. The collection stays alive.

let v = vec![1, 2, 3];
// iter() borrows the vector. v remains usable.
for x in v.iter() {
    println!("{}", x);
}
println!("{:?}", v); // OK

Reference loops and types

for x in v calls into_iter() on v, yielding owned values. for x in &v calls into_iter() on &v, yielding references. Mixing these up causes type mismatches.

let v = vec![1, 2, 3];
let mut results = Vec::new();

// &v yields &i32. results expects i32.
for x in &v {
    results.push(x); // Error
}

The compiler rejects this with E0308 (mismatched types). x is &i32, but push expects i32. You can fix this by dereferencing: results.push(*x), or by iterating over the owned vector: for x in v. Choose based on whether you need the data later.

Implementing IntoIterator without Iterator

You cannot implement IntoIterator alone. The return type of into_iter() must implement Iterator. If you try to return a type that doesn't implement Iterator, the compiler complains about trait bounds.

impl IntoIterator for MyVec {
    type Item = i32;
    type IntoIter = Vec<i32>; // Vec does not implement Iterator

    fn into_iter(self) -> Self::IntoIter {
        self.data
    }
}

This fails because Vec<i32> does not implement Iterator. You must return a type that implements Iterator, like std::vec::IntoIter<i32> or a custom struct.

Trust the borrow checker here. If you need the data after iteration, reach for .iter(). If you need to mutate, reach for .iter_mut(). Reserve .into_iter() for when you truly want to consume the collection.

Decision matrix

Use Iterator when you are building the engine that produces items one by one. Implement next() to define how the stream advances.

Use IntoIterator when you have a collection or container and want to enable for loops and iterator adapters. Implement into_iter() to return a type that implements Iterator.

Use impl IntoIterator in function arguments when you want maximum flexibility for callers. This accepts vectors, slices, existing iterators, and tuples without forcing the caller to convert.

Use impl Iterator in function arguments when you already have a stream and don't want to force a conversion. This is slightly more efficient if the caller is already iterating, but less convenient for passing raw collections.

Use .into_iter() when you need to consume the collection and take ownership of the items.

Use .iter() when you only need to read the items and keep the collection alive.

Use .iter_mut() when you need to modify the items in place.

Counter-intuitive but true: accepting IntoIterator often makes your function easier to use, even if Iterator feels more pure. Callers appreciate not having to call .into_iter() manually.

Where to go next