FromIterator and collect
.collect() is the workhorse ending of nearly every iterator chain from Iterators Over Loops, but it does something subtler than "make a Vec." collect is generic over its return type: the same call can build a Vec, a HashMap, a String, or short-circuit into a Result, and it's driven entirely by the type you ask for. The trait behind this is FromIterator, and understanding it explains collect's "magic" type inference, unlocks collecting into far more than Vec, and lets your own types be collect targets. This tutorial covers how collect really works and the patterns that surprise people.
collect Is Generic Over Its Result
collect has no idea what to build until you tell it. Its signature is roughly fn collect<B: FromIterator<Self::Item>>(self) -> B, the return type B is a type parameter, and any type implementing FromIterator can be that B. This is why collect calls need a type annotation the compiler can't otherwise infer:
let nums = vec![1, 2, 3];
// the type annotation drives which FromIterator impl is used
let doubled: Vec<i32> = nums.iter().map(|n| n * 2).collect();
// turbofish is the alternative: name the target on collect itself
let doubled = nums.iter().map(|n| n * 2).collect::<Vec<i32>>();
Both forms tell collect the same thing. When you get error[E0282]: type annotations needed, it's because collect genuinely can't guess the target, add : Vec<_> on the binding, or ::<Vec<_>> on the call (the "turbofish"). The _ lets the compiler infer the element type while you specify the container.
Collecting Into More Than Vec
Because any FromIterator type is a valid target, collect builds many collections directly, no manual loop needed. The most useful beyond Vec:
use std::collections::{HashMap, HashSet};
// an iterator of pairs → a HashMap
let scores: HashMap<&str, i32> =
[("alice", 90), ("bob", 85)].into_iter().collect();
// deduplicate by collecting into a HashSet
let unique: HashSet<i32> = vec![1, 2, 2, 3, 3, 3].into_iter().collect();
// chars/strings → a String
let shout: String = "hello".chars().map(|c| c.to_ascii_uppercase()).collect();
Collecting an iterator of (K, V) tuples straight into a HashMap is the idiom for building a lookup table from a transformation, far cleaner than a loop with .insert(). Collecting into a HashSet deduplicates in one step. And collecting chars (or &strs) into a String avoids building an intermediate Vec<char>. The target type is the only thing that changes; the chain before collect is identical.
Collecting Into Result and Option: Short-Circuiting
The most powerful FromIterator impl is the one for Result. An iterator of Result<T, E> can collect into a Result<Vec<T>, E>: if every item is Ok, you get Ok(Vec<T>); if any is Err, collection stops and returns the first Err. This turns "parse all of these, failing fast" into one line:
// each parse returns Result — collecting into Result<Vec<_>, _> short-circuits
let numbers: Result<Vec<i32>, _> =
vec!["1", "2", "3"].into_iter().map(|s| s.parse::<i32>()).collect();
// Ok([1, 2, 3])
let with_bad: Result<Vec<i32>, _> =
vec!["1", "oops", "3"].into_iter().map(|s| s.parse::<i32>()).collect();
// Err(ParseIntError) — stopped at "oops", the 3 is never parsed
Gotcha: the target type controls the behavior entirely, and it's easy to get the nesting wrong.
collect::<Result<Vec<_>, _>>()gives fail-fast (first error wins, discarding the rest);collect::<Vec<Result<_, _>>>()collects every result including all errors, no short-circuit. If you want to keep the successes and the failures, collect intoVec<Result<_, _>>and partition; if you want "all-or-nothing," collect intoResult<Vec<_>, _>. Picking the wrong one silently changes whether errors abort or accumulate.Optionworks the same way: an iterator ofOption<T>collects intoOption<Vec<T>>,Noneif any element isNone.
Implementing FromIterator for Your Own Type
If you write a collection type, implementing FromIterator makes it a collect target like any built-in. You also usually implement Extend (add items to an existing instance), the two are close cousins, and FromIterator is often written in terms of Extend:
struct Stats { count: usize, sum: i64 }
impl FromIterator<i64> for Stats {
fn from_iter<I: IntoIterator<Item = i64>>(iter: I) -> Self {
let mut count = 0;
let mut sum = 0;
for x in iter {
count += 1;
sum += x;
}
Stats { count, sum }
}
}
// now Stats is a valid collect target:
let stats: Stats = vec![10, 20, 30].into_iter().collect();
// stats.count == 3, stats.sum == 60
FromIterator doesn't have to build a container, here it folds an iterator into a summary. The from_iter method takes impl IntoIterator (Implementing Your Own Iterator) so callers can pass anything iterable. Implementing it is what lets a domain type participate in the same ergonomic .collect() pipeline as Vec, rather than requiring a bespoke constructor.
collect Targets at a Glance
| Iterator of | Collect into | Result |
|---|---|---|
T | Vec<T> | a growable list |
T | HashSet<T> / BTreeSet<T> | deduplicated set |
(K, V) | HashMap<K, V> / BTreeMap<K, V> | a lookup table |
char / &str / String | String | concatenated text |
Result<T, E> | Result<Vec<T>, E> | fail-fast (first Err) |
Result<T, E> | Vec<Result<T, E>> | keep all (no short-circuit) |
Option<T> | Option<Vec<T>> | None if any is None |
| anything | a custom FromIterator type | whatever you define |
The mental model: collect is a shape-shifter driven by its return type, and FromIterator is the trait that lists the shapes it can take. When a chain needs a specific output, you rarely need a manual loop, ask collect for the type you want. And remember the Result<Vec> vs Vec<Result> distinction: it's the difference between "fail on the first error" and "gather every outcome."
Key Takeaways
collectis generic over its return type viaFromIterator; the target type (from an annotation or turbofish::<T>) decides what gets built, which is whycollectcalls so often need a type hint.- Collect into far more than
Vec:(K, V)pairs into aHashMap, values into aHashSetto deduplicate,chars into aString, all by changing only the target type. - An iterator of
Result<T, E>collects intoResult<Vec<T>, E>and short-circuits on the firstErr(all-or-nothing);Optionbehaves the same. This is the idiomatic "do all of these, fail fast" pattern. - Choose nesting deliberately:
Result<Vec<_>, _>fails fast and discards the rest, whileVec<Result<_, _>>keeps every success and error, the target type silently decides whether errors abort or accumulate. - Implement
FromIterator(and usuallyExtend) to make your own type acollecttarget; it can build a container or fold into a summary, and takesimpl IntoIteratorso any iterable works.