How to Collect an Iterator into Different Collection Types

Convert Rust iterators into Vec, String, or HashMap using the collect() method with type inference.

The bucket at the end of the pipe

You are processing a stream of data. You filtered out the noise. You mapped the raw bytes into structured objects. Now you need to keep the results. The iterator yields items one by one, but an iterator is not a container. You cannot store an iterator in a variable and pass it around like a list. You need to pour the stream into a bucket.

collect() is that bucket. It consumes the iterator and builds a collection. The method sits at the end of iterator chains. It forces the lazy evaluation to happen and materializes the results into a concrete type like Vec, String, HashMap, or Result.

How collect knows what to build

An iterator doesn't know what collection you want. It only knows how to produce the next item. collect() needs a target type to work. You provide that type through a type annotation. The compiler uses the annotation to find the right implementation of the FromIterator trait.

Think of collect() as a packaging station on a factory line. Items come down the conveyor belt. The station needs a template to pack them. You hand the station a template by specifying the type. If you hand it a Vec template, it builds a vector. If you hand it a HashMap template, it builds a map. If you don't hand it a template, the station stops and asks you what you want.

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

    // Type annotation tells collect which FromIterator impl to use.
    // Without : Vec<i32>, the compiler cannot choose the target type.
    let result: Vec<i32> = numbers.iter().copied().collect();

    println!("{:?}", result);
}

The copied() call converts &i32 references into i32 values. The type annotation Vec<i32> tells the compiler to look for impl FromIterator<i32> for Vec<i32>. The compiler finds it and generates the code to build the vector.

The FromIterator trait

collect() is defined on the Iterator trait, but the actual work happens in the FromIterator trait. Every collection type that supports collect() implements FromIterator. The trait looks like this:

pub trait FromIterator<A> {
    fn from_iter<T: IntoIterator<Item = A>>(iter: T) -> Self;
}

The trait takes an iterator of items A and returns Self. When you write iterator.collect::<Vec<i32>>(), the compiler resolves Self to Vec<i32> and A to i32. It calls Vec::from_iter(iterator).

You can implement FromIterator for your own types. If you have a custom data structure, adding this implementation lets users collect iterators directly into it. This is a standard convention in Rust libraries. It makes your type feel like a first-class citizen alongside Vec and HashMap.

Real-world patterns

Collecting into a Vec is the most common case, but collect() shines in other scenarios. It handles complex types with the same syntax.

Collecting Results

When you have an iterator of Result values, collect() does something clever. It doesn't just give you a Vec<Result<T, E>>. It gives you a Result<Vec<T>, E>. If every item is Ok, you get Ok(vec![...]). If any item is Err, the collection short-circuits and returns that error.

fn parse_number(s: &str) -> Result<i32, &str> {
    s.parse().map_err(|_| s)
}

fn main() {
    let inputs = vec!["1", "2", "bad", "4"];

    // collect() transforms Iterator<Item = Result<T, E>> into Result<Vec<T>, E>.
    // The first Err stops the collection and returns immediately.
    let result: Result<Vec<i32>, &str> = inputs.iter().map(|s| parse_number(s)).collect();

    match result {
        Ok(numbers) => println!("Parsed: {:?}", numbers),
        Err(err) => println!("Failed at: {}", err),
    }
}

This pattern eliminates boilerplate. You don't need a loop that checks every result and breaks on error. The trait implementation handles the control flow.

Building Maps

You can collect pairs directly into a HashMap or BTreeMap. The iterator must yield (Key, Value) tuples.

use std::collections::HashMap;

fn main() {
    let pairs = vec![("a", 1), ("b", 2), ("c", 3)];

    // The iterator yields (&str, i32) tuples.
    // HashMap implements FromIterator<(K, V)>.
    let map: HashMap<&str, i32> = pairs.iter().map(|(k, v)| (*k, *v)).collect();

    println!("{:?}", map.get("b"));
}

The map call dereferences the tuple elements. pairs.iter() produces &(K, V). The map converts that to (K, V) by dereferencing. The collect call consumes the tuples and inserts them into the map.

String collection

Collecting into a String has a strict requirement. The iterator must yield char values. You cannot collect &str slices directly into a String. The compiler enforces this because String is a UTF-8 container. A char is guaranteed to be a valid Unicode scalar value. A &str could be anything. The FromIterator implementation for String only accepts char to guarantee safety.

fn main() {
    // chars() yields char values, which String can collect.
    let greeting: String = "hello".chars().collect();

    println!("{}", greeting);
}

If you try to collect &str values, the compiler rejects the code. You must convert the slices to characters or join them explicitly.

Common pitfalls

Missing type annotations

The most frequent error is forgetting the type annotation. collect() cannot infer the target type from the iterator alone. Many types implement FromIterator for the same item type. The compiler needs you to disambiguate.

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

    // This fails with E0283: type annotations needed.
    // The compiler sees multiple possible targets for collect().
    let result = numbers.iter().copied().collect();
}

The compiler rejects this with E0283 (type annotations needed). It lists the candidates. You must add : Vec<i32> or : HashSet<i32> to resolve the ambiguity.

String vs Vec

Another common mistake is trying to collect string slices into a String.

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

    // This fails with E0277: the trait bound String: FromIterator<&str> is not satisfied.
    let combined: String = words.iter().collect();
}

The compiler rejects this with E0277 (trait bound not satisfied). String does not implement FromIterator<&str>. You need FromIterator<char>. To join strings, use join or collect into a Vec<String> first.

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

    // Collect into Vec<String>, then join.
    let vec: Vec<String> = words.iter().map(|s| s.to_string()).collect();
    let combined = vec.join(" ");

    println!("{}", combined);
}

References vs values

Be careful with iter() versus into_iter(). iter() produces references. into_iter() produces values. If you collect references, you get a Vec<&T>. If you collect values, you get a Vec<T>. The lifetime of the references must outlive the vector.

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

    // Collecting references creates a Vec<&i32>.
    // The vector borrows from data.
    let refs: Vec<&i32> = data.iter().collect();

    // data must stay alive while refs is used.
    println!("{:?}", refs);
}

If you try to return the vector of references from a function, you'll hit lifetime errors. The data would be dropped when the function returns, leaving dangling references. Use into_iter() or copied() to own the values.

When to collect and when not to

Use collect() when you need to materialize the entire iterator into a concrete collection like Vec, String, HashMap, or Result<Vec<T>, E>. Use for loops when you only need to process items side-by-side and don't need to store the results. Pick fold() when you are building a custom accumulator that doesn't implement FromIterator. Use into_iter() before collect() when you want to move values out of the source collection rather than copying or referencing them.

Don't collect if you don't need the collection. Iterators are lazy. They do no work until you pull items from them. collect() forces the entire chain to execute. If you only need the first matching item, use find(). If you only need to check a condition, use any() or all(). Collecting everything just to read one value wastes memory and CPU cycles.

If the compiler asks for a type, give it one. collect refuses to guess.

Where to go next