How to fix Rust E0277 Iterator not implemented

Fix Rust E0277 by calling .iter() on your collection to implement the Iterator trait before using iterator methods.

The container isn't the stream

You grab a Vec of numbers and try to chain .map() onto it. The compiler throws E0277: trait bound not satisfied. It tells you Vec<i32> does not implement Iterator. This feels like a bug. In Python, you can map over a list directly. In JavaScript, arrays have .map() built in. Rust says no. The list and the iterator are two different things.

The compiler is right. It is not being difficult. It is enforcing a design choice that enables zero-cost abstractions and flexible generic code. Rust separates storage from traversal. A Vec stores data. An Iterator provides a sequence of items. You must convert the storage into a stream before you can chain iterator methods.

Storage versus traversal

Think of a Vec as a sealed deck of cards. It holds all the cards in order, but it does not know how to give them to you one at a time. An Iterator is the dealer. The dealer takes the deck and hands out cards sequentially. The deck stores the data. The iterator provides the sequence.

Rust keeps these roles separate because it allows the compiler to optimize how you walk through data without changing how the data is stored. A Vec might be stored in a contiguous block of memory. A HashMap stores data in buckets. A LinkedList scatters nodes across the heap. Each collection needs a different way to traverse its items. By defining a common Iterator trait, Rust lets you write code that works with any collection, regardless of how it stores data.

The Iterator trait defines a single method: next(). Every iterator must be able to produce the next item in the sequence. When the sequence is exhausted, next() returns None. Methods like .map(), .filter(), and .collect() are defined in terms of next(). They work on any type that implements Iterator.

The minimal fix

To use iterator methods on a Vec, you must call a method that returns an iterator. The most common method is .iter(). This creates an iterator that borrows the Vec and yields references to the items.

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

// This fails: Vec does not implement Iterator.
// The compiler rejects this with E0277 (trait bound not satisfied).
// numbers.map(|n| n * 2);

// Fix: Call .iter() to create an iterator over references.
let doubled: Vec<_> = numbers
    .iter() // Creates an iterator that yields &i32
    .map(|n| n * 2) // Transforms each reference to a new value
    .collect(); // Gathers results into a new Vec

println!("{:?}", doubled); // [2, 4, 6]

The .iter() call returns a struct called std::slice::Iter. This struct holds a pointer to the start of the slice and a pointer to the current position. It implements the Iterator trait. The compiler sees that Iter satisfies the trait bound required by .map(). The error disappears.

Call .iter() to turn storage into a stream.

How the iterator struct works

When you call .iter(), you are not copying the data. You are creating a small wrapper struct. This struct contains two pointers: one to the beginning of the collection and one to the current index. The struct implements next().

Each time next() is called, the struct checks if the current index is past the end. If it is, next() returns None. Otherwise, it returns Some containing a reference to the item at the current index, then advances the index by one.

This design is efficient. The iterator struct is tiny. It fits in a register. The traversal logic is inlined by the compiler. There is no virtual dispatch overhead. The iterator is a zero-cost abstraction. It provides the convenience of high-level iteration without the runtime cost.

The compiler generates the loop code based on the Iterator trait. When you use .collect(), the compiler calls next() repeatedly until it returns None. The loop is unrolled and optimized just like a hand-written for loop.

The lazy pipeline

Iterator methods in Rust are lazy. Calling .map() does not execute the closure immediately. It returns a new iterator struct that wraps the previous iterator. The work happens only when you drive the iterator to completion.

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

// This creates a pipeline of iterator structs.
// No items are processed yet.
let pipeline = numbers
    .iter()
    .filter(|n| n % 2 == 0) // Keeps even numbers
    .map(|n| n * 10);      // Multiplies by 10

// The pipeline is still lazy. Nothing has run.
// Only when we call .collect() does the work happen.
let result: Vec<_> = pipeline.collect();

println!("{:?}", result); // [20, 40]

Laziness is a feature. It lets you chain operations without creating intermediate collections. You can filter, map, and take items in a single pass. The compiler generates a tight loop that applies all transformations as it walks through the data. This saves memory and improves cache performance.

If you need to execute the pipeline immediately, call .collect() or use a for loop. The for loop drives the iterator until it is exhausted.

Laziness is a feature, not a bug. Chain your operations and let the compiler optimize the loop.

IntoIterator and the for loop

You might notice that for loops work on Vec without calling .iter(). This is because the for loop uses a different trait: IntoIterator.

The for loop desugars to code that calls .into_iter(). The IntoIterator trait converts a value into an iterator. Vec implements IntoIterator, but it does not implement Iterator. This separation allows Vec to work in loops while keeping the storage and traversal roles distinct.

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

// The compiler rewrites this loop:
// for n in numbers.into_iter() { ... }
for n in numbers {
    println!("{}", n);
}

The .into_iter() method consumes the Vec and yields owned items. This is different from .iter(), which borrows the Vec and yields references. The for loop chooses .into_iter() by default because it is the most flexible option. It works for owned values, references, and mutable references.

If you try to use the Vec after the loop, the compiler rejects you with E0382 (use of moved value). The Vec was consumed by .into_iter(). To keep the Vec alive, use a for loop over a reference: for n in &numbers. The compiler then calls .into_iter() on the reference, which yields references to the items.

The for loop handles the conversion for you. Trust the desugaring.

Real-world: flexible function signatures

The IntoIterator trait enables flexible function signatures. You can write a function that accepts any collection by requiring IntoIterator. The function can then use a for loop to process the items.

/// Prints all items from any collection that can be converted to an iterator.
fn print_items<I>(items: I)
where
    I: IntoIterator,
    I::Item: std::fmt::Display,
{
    // The compiler converts `items` to an iterator automatically.
    for item in items {
        println!("Item: {}", item);
    }
}

fn main() {
    let vec = vec![1, 2, 3];
    let slice = &[4, 5, 6];
    let set = [7, 8, 9];

    // All three calls work because Vec, &[T], and [T; N] implement IntoIterator.
    print_items(vec);
    print_items(slice);
    print_items(set);
}

This pattern is common in Rust libraries. Functions that process sequences often accept IntoIterator instead of a specific collection type. This allows callers to pass Vec, slices, arrays, or custom iterators without writing adapters.

When you write a function that processes a sequence, prefer IntoIterator in the signature. It makes your API more flexible and easier to use.

Pitfalls and compiler signals

The most common mistake is calling iterator methods on a collection directly. If you forget .iter(), the compiler rejects you with E0277 (trait bound not satisfied). It says the type does not implement Iterator. The fix is to add .iter(), .iter_mut(), or .into_iter().

Another mistake is using .into_iter() when you need to keep the data. .into_iter() consumes the collection. If you try to use the collection later, you get E0382 (use of moved value). Use .iter() when you only need to read the items. Use .into_iter() when you want to take ownership.

A third mistake is mixing borrows and moves. If you call .iter() and then try to move items out of the iterator, the compiler rejects you with E0507 (cannot move out of borrowed content). The iterator yields references. You cannot move data through a reference. Use .into_iter() to move items.

Convention aside: the community prefers .iter() for reading and .into_iter() for consuming. When you see .iter() in code, assume the collection is borrowed. When you see .into_iter(), assume the collection is moved. This convention makes code easier to read.

Check the method signature. If it asks for IntoIterator, you have options. If it asks for Iterator, you must provide an iterator.

Choosing the right conversion

Use .iter() when you need to read the items without moving them. This borrows the collection and yields &T. Use .iter_mut() when you need to modify the items in place. This borrows the collection mutably and yields &mut T. Use .into_iter() when you want to take ownership of the items. This consumes the collection and yields T. Use a for loop when you just need to run code for each item and don't need to chain methods. The loop handles the iterator conversion automatically. Use .enumerate() when you need the index along with the item. This wraps the iterator and yields (usize, T). Use IntoIterator as a trait bound when writing generic functions that accept any collection. This makes your API flexible and easy to use.

Pick the conversion that matches your ownership needs. The compiler will guide you if you get it wrong.

Where to go next