collect and FromIterator¶
Level: 201 · working knowledge
One line: collect has no behaviour of its own — it asks the type you asked for to build itself from your iterator, which is why the same call produces a Vec, a String, a deduplicated set, a map, or one Result covering every row.
fn main() {
let words = ["Ada", "Ben", "Cara", "Ada"];
let v: Vec<&str> = words.into_iter().collect();
let s: String = words.into_iter().collect();
println!("{v:?} {s:?}"); // ["Ada", "Ben", "Cara", "Ada"] "AdaBenCaraAda"
}
One iterator, two annotations, two different programs. The method is a one-liner:
Everything interesting is in B.
The type has to be determined, not written¶
Because the target does the work, the compiler cannot infer it from the iterator alone — E0282, type annotations needed is the most common collect error, and it means "which collection?", not "which element?".
But "annotate the collect" is the rule as usually taught, and it is a rule about the wrong thing. What collect needs is for something to pin B. Three things can:
let v: Vec<&str> = words.into_iter().collect(); // 1. the binding
let n = words.into_iter().collect::<Vec<_>>().len(); // 2. the turbofish
let report = Report { lines: words.into_iter().map(str::to_uppercase).collect() };
// 3. the USE SITE — nothing named here
The third is the one that surprises people, and it is not a special case: inference runs backward from wherever the value lands, so a struct field, a function parameter, or a return type determines B exactly as well as a binding does. In fn shout(w: &[&str]) -> Vec<String> { w.iter().map(|s| s.to_uppercase()).collect() } the signature is the annotation.
Use the turbofish when nothing downstream pins it — usually because you are about to call a method on the result. The _ is inference doing the element type, which it can work out; only the container has to be named. That is also the cheapest way to explore an unfamiliar API: write let x: Vec<_> = … and let rustc or your editor tell you what the elements turned out to be.
The one use site that does not pin it is a slice parameter — which is the parameter type Rust otherwise tells you to prefer, and the one clippy::ptr_arg (warn by default: "fn arguments of the type &Vec<...> or &String, suggesting to use &[...] or &str instead") will push you toward. Given fn widest(lines: &[String]) -> usize, this fails:
let lines = words.into_iter().map(str::to_uppercase).collect(); // ← does not compile
println!("{}", widest(&lines));
error[E0277]: a slice of type `[String]` cannot be built since `[String]` has no definite size
|
7 | let lines = words.into_iter().map(str::to_uppercase).collect();
| ^^^^^^^ try explicitly collecting into a `Vec<String>`
|
= help: the trait `FromIterator<String>` is not implemented for `[String]`
&Vec<String> reaches &[String] by a deref coercion, and inference will not run a coercion backward — it takes [String] at face value and reports it unsized. Both Vec<String> and &Vec<String> do pin it, but the second is the exact signature ptr_arg just told you not to write, so the honest options are a by-value Vec<String> parameter or naming the type at the collect — which is what the compiler's own suggestion says. That is the small collision worth remembering: the idiomatic parameter type and use-site inference do not compose, and the lint wins.
What you can collect into¶
| Target | What it does that a Vec does not |
|---|---|
String ↗ |
joins chars or &strs with no separator |
HashSet<T> ↗ / BTreeSet<T> ↗ |
drops duplicates; BTreeSet also sorts |
HashMap<K, V> ↗ / BTreeMap<K, V> ↗ |
from an iterator of pairs; a later key overwrites an earlier one |
VecDeque<T> ↗, BinaryHeap<T> ↗, LinkedList<T> ↗ |
the other std containers, same call |
Result<Vec<T>, E> |
one Result for the whole sequence — see below |
Option<Vec<T>> |
the same, for absence rather than failure |
(Vec<A>, Vec<B>) |
from an iterator of pairs, via unzip |
() |
discards the items; useful only in Result<(), E> |
The map row is the one that bites: collect is not a merge. [("Ada", 5), ("Ben", 3), ("Ada", 4)] collects to {"Ada": 4, "Ben": 3} — the second Ada overwrote the first, silently. When the duplicates should combine, fold with entry().or_insert() is the tool.
The one everybody needs: Result<Vec<_>, _>¶
An iterator of Results collects into one Result of a collection:
fn main() {
let rows = ["5", "3", "0"];
let parsed: Result<Vec<i32>, _> = rows.into_iter().map(str::parse::<i32>).collect();
println!("{parsed:?}"); // Ok([5, 3, 0])
}
Three things this buys, and one it costs:
- The shape flips.
Iterator<Item = Result<T, E>>becomesResult<Vec<T>, E>, so the caller has one thing to check instead of one per row. This is the standard way to parse a file of numbers into aVec<i32>with a?at the end. - It short-circuits. On
["5", "no", "0", "also no"]the closure runs twice, not four times.collectstops at the firstErr. ?works on the result, which is usually the whole point of the line.- Only the first error survives. If you need all of them — a validation report, a row-by-row error list —
collectis the wrong consumer. Usepartition(Result::is_ok), or collect intoVec<Result<T, E>>and sort it out afterwards. On the four rows above, partitioning keeps two errors where collecting kept one.
The un-flipped shape is one annotation away, on the same pipeline: ask for Vec<Result<i32, _>> instead and you get [Ok(5), Err(..), Ok(0), Err(..)] — all four rows run, every outcome kept, no short-circuit, because there is nothing to short-circuit to. Which of the two you want is the whole decision, and it is made in the type.
Option<Vec<T>> behaves identically, with None in place of Err. If you have met this shape in a functional language, it is traverse/sequence ↗ — Rust gets it from one FromIterator impl rather than a separate combinator.
Result<(), E>: the degenerate case that is actually useful¶
There is a FromIterator impl for (), so a sequence of fallible steps whose successes carry no value collects into a single "did it all work":
Ok(()) or the first Err, nothing else kept. That is the right shape for a run of side effects — writes, validations, sends — and it is also the reason a stray let _: () = it.collect(); compiles and silently does nothing.
Making your own type collectable¶
One trait, one method:
impl FromIterator<String> for Roster {
fn from_iter<I: IntoIterator<Item = String>>(iter: I) -> Self {
let mut rows = Vec::new();
let mut longest = 0;
for row in iter {
longest = longest.max(row.len());
rows.push(row);
}
Roster { rows, longest }
}
}
and let roster: Roster = names.into_iter().map(String::from).collect(); now works. Note the parameter is IntoIterator, not Iterator — the same reason a collection is never itself an iterator.
What it costs¶
collect allocates a new collection every time. Two ways to pay less:
extendpours into a collection you already have — the same work without a fresh allocation, and the natural form inside a loop that accumulates across iterations.- An honest
size_hintlets either one make a single allocation instead of growing by doubling. For a slice-backed iterator you get that free; for a hand-written one it is three lines you have to write.
And a collection you collect into is a claim about your element type, checked at the call: HashSet needs Eq + Hash, BTreeSet needs Ord. The error arrives on the collect line rather than on the type definition, which is confusing exactly once.
If you are coming from another language¶
- Python.
list(gen),set(gen),dict(pairs),"".join(gen)— Python names the target as a constructor call and Rust names it as a type, but it is the same decision made in the same place, and the dict case has the same last-key-wins behaviour. What Python has no counterpart for isResult<Vec<_>, _>: gathering per-row failures into one answer is atry/exceptaround the whole loop, which is coarser (you lose which row) or a manual accumulator (which ispartition). And Python'slist()cannot be extended to your own class in the wayFromIteratorextendscollect— the nearest thing is writingMyType(gen)and accepting the iterable in__init__, which works but is not the same generic call site. - ABAP. There is no equivalent, and that is worth saying plainly: building a result collection is always a
LOOPwith anAPPEND, or the 7.40+ table comprehensionVALUE ty_tab( FOR wa IN lt_src ( CORRESPONDING #( wa ) ) ), which is the closest ABAP gets — and note it names the target type on the left exactly as Rust does. The pieces with no ABAP counterpart are the interesting ones:HashSetdeduplication isDELETE ADJACENT DUPLICATESafter aSORT, a separate statement rather than a choice of target; andResult<Vec<_>, _>has no analogue at all, since ABAP's failure channel issy-subrcor an exception, neither of which composes over a table. The habit that transfers badly is checkingsy-subrcper row inside the loop; the Rust shape hoists that decision to the collect. - Java / C#.
.collect(Collectors.toList())and.ToList()are the same idea, and Java'sCollectorisFromIteratorwith more moving parts. The difference is where the type comes from: Java passes a collector value, Rust infers the impl from the annotation, which is why Rust's failure mode is "type annotations needed" and Java's is picking the wrong collector. The map case is where the two designs diverge most. Java's two-argumentCollectors.toMapthrows — "anIllegalStateExceptionis thrown when the collection operation is performed" if the mapped keys contain duplicates — so handling them means a three-argument overload with a merge function, and choosing the map implementation means a four-argument one with a supplier. C#'sToDictionarythrows the same way. Rust'scollectdoes none of that: it takes the target from the annotation and silently overwrites on a duplicate key. Fewer knobs, but the duplicate-key behaviour you get by default is the one Java made you ask for explicitly — worth knowing in both directions when porting.
The verified output¶
Verified output of collect_and_fromiterator.rs — regenerated by tools/run_examples.py, never hand-typed.
1. One call, and the TYPE decides what gets built
Vec<&str> ["Ada", "Ben", "Cara", "Ada"]
String "AdaBenCaraAda"
BTreeSet<&str> {"Ada", "Ben", "Cara"} (sorted, and the duplicate Ada is gone)
VecDeque<&str> ["Ada", "Ben", "Cara", "Ada"]
Same iterator four times. `collect` did not decide any of this;
the annotation did, by picking whose FromIterator impl runs.
2. Pairs collect into a map
BTreeMap {"Ada": 4, "Ben": 3}
Ada appears twice in the input and once here: for a map, a later
key overwrites an earlier one. `collect` is not a merge — reach
for `fold` with `entry().or_insert()` when the totals matter.
3. Turbofish, when there is no variable to annotate
.collect::<Vec<_>>().len() = 4
the `_` is inference doing the element type; only the container
has to be named, and often only in one of the two places.
4. The one everybody needs: Result<Vec<_>, _> from fallible rows
good rows -> Ok([5, 3, 0])
bad rows -> Err(invalid digit found in string)
and it stopped after 2 of 4 rows — collect into a Result is
short-circuiting. Note the shape it flipped: an iterator OF
Results became one Result OF a Vec, so the caller has one thing
to check instead of one per row.
Same pipeline, one annotation apart:
Vec<Result<_, _>> -> [Ok(5), Err(..), Ok(0), Err(..)]
all 4 rows ran and every outcome is kept — the un-flipped shape
has nothing to short-circuit TO, so it does not.
Only the FIRST error survives. To keep them all, collect into a
(Vec<_>, Vec<_>) with partition, or Vec<Result<_, _>> and sort it out.
partitioned -> 2 ok, 2 err (every error kept)
5. Option collects the same way, and so does anything else you write
all even -> Some([2, 4, 6])
one odd -> None (one None and the whole answer is None)
our own -> Roster with 3 rows, longest 10
one `impl FromIterator<String> for Roster` and `.collect()` works
on it — the trait is the whole extension point.
6. What collect costs, and the two ways to pay less
collect -> [3, 3, 4, 3] capacity 4
extend -> [3, 3, 4, 3] capacity 4
`collect` allocates a new collection every time. `extend` pours
into one you already have, which is the loop-friendly form; and
an exact `size_hint` is what lets either make one allocation.
7. The useful degenerate case: Result<(), E>
all rows parse -> Ok(())
one row does not -> Err(invalid digit found in string)
There is a FromIterator impl for `()`, so collecting an iterator
of `Result<(), E>` gives one `Result<(), E>`: did every step work,
with the first failure and nothing else kept. That is the shape
for a run of fallible side effects whose successes carry no value.
Same trick, same warning: it stops at the first Err.
8. Deduplicating is a collect, and it costs a trait or two
three names -> HashSet<Voter> -> ["Ada", "Ben"]
`Voter` had to derive Eq and Hash to land in a HashSet, and Ord
to land in a BTreeSet. Which collection you collect into is a
claim about your type, checked at the collect call.
9. You do not have to WRITE the type — it has to be KNOWABLE
struct field -> ["ADA", "BEN", "CARA", "ADA"]
return position -> ["ADA", "BEN", "CARA", "ADA"]
by-value arg -> widest = 4
Not one of those three `collect` calls names a type, and all
three compile: inference runs BACKWARD from where the value
lands. An annotation is not the requirement — a DETERMINED type
is, and a struct field, a return type or a parameter determines
one just as well as a binding does.
The use site that does NOT work is a `&[String]` parameter, which
is the one Rust otherwise tells you to prefer: `&Vec<String>` ->
`&[String]` is a deref coercion, and inference will not run a
coercion backward. It takes `[String]` literally and rejects it
as unsized — so that call is where you go back to a turbofish.
See also¶
- Collect the iterator into a
Vec— the prior question: whether to materialize at all, and what theVecis bought with foldandreduce— what to use when the duplicates have to combine rather than overwrite- Iterators are lazy —
collectis the consumer that makes the chain run - Implementing
Iterator—size_hint, and whyfrom_itertakes anIntoIterator Optionis a one-item collection — the same flip, on a sequence of length 0 or 1OptionvsResult— which of the two collects you want- Making a
String—collect::<String>()beside the four other spellings
Sources¶
Iterator::collect ↗ and FromIterator ↗ — the std page for the trait lists every impl, which is the real answer to "what can I collect into".
Sam Van Overmeire, The Many Neat Tricks of Rust's collect ↗ (Feb 2026) — a blog post, and the source of the use-site-inference point above; its closing example drops the annotation entirely and lets a struct field supply it. The slice-parameter limit and the clippy::ptr_arg collision are this page's, measured on the pinned toolchain. Its Java comparison is where the Collectors.toMap escalation comes from; the throwing behaviour is quoted from the Collectors javadoc ↗ rather than the post.
Po polsku¶
Polski czasownik podpowiada tu złą intuicję. „Zebrać” brzmi jak czynność, którą wykonuje collect — a collect nie robi nic własnego: jego ciało to jedna linijka, FromIterator::from_iter(self), czyli oddanie roboty typowi, o który poprosiłeś. Decyzja jest po lewej stronie, w adnotacji, a nie po prawej, w wywołaniu. Ten sam iterator zebrany czterokrotnie daje Vec<&str>, String (sklejony, bez separatora), BTreeSet (posortowany i bez powtórzonej Ada) oraz VecDeque — nie dlatego, że collect je rozróżnia, tylko dlatego, że za każdym razem uruchamia inną implementację FromIterator.
Stąd bierze się najczęstszy błąd przy tej metodzie: E0282, type annotations needed. Po polsku mówimy „brakuje adnotacji typu”, co jest prawdą, ale myli — kompilator nie pyta o typ elementów (ten potrafi wywnioskować), tylko o to, do jakiej kolekcji ma je włożyć. I reguła nie brzmi „opisuj każdy collect”, tylko „typ musi dać się ustalić”: wystarczy wiązanie (let v: Vec<_>), turbofish (collect::<Vec<_>>()), ale tak samo dobrze pole struktury, typ zwracany funkcji albo typ parametru — wnioskowanie biegnie wstecz, od miejsca, w którym wartość ostatecznie ląduje. Jeden wyjątek warto zapamiętać, bo jest złośliwy: parametr będący wycinkiem (slice), czyli &[String] — a więc dokładnie ten typ parametru, do którego popycha clippy::ptr_arg. &Vec<String> dochodzi do &[String] przez koercję dereferencji (deref coercion), a wnioskowanie nie odtwarza koercji wstecz: bierze [String] dosłownie i odrzuca jako typ bez znanego rozmiaru (E0277). Idiomatyczny parametr i wnioskowanie z miejsca użycia po prostu się tu nie składają — wtedy wraca się do turbofisha.
Najważniejszy pojedynczy wzorzec to Result<Vec<T>, E>, bo odwraca kształt: iterator Resultów staje się jednym Resultem na całą sekwencję, więc wołający ma do sprawdzenia jedną rzecz zamiast jednej na wiersz, a na końcu linii można postawić ?. Dwie konsekwencje, o których polskie materiały zwykle milczą: collect przerywa na pierwszym błędzie (short-circuit) — na czterech wierszach domknięcie (closure) odpali się dwa razy, nie cztery — i zostaje wyłącznie pierwszy błąd. Jeśli potrzebujesz raportu ze wszystkimi, to nie ten konsument: partition(Result::is_ok) albo Vec<Result<T, E>> zachowa komplet, przy czym ten drugi kształt jest o jedną adnotację obok, na tym samym łańcuchu, i daje [Ok(5), Err(..), Ok(0), Err(..)] — nie ma tam czego przerywać. Tak samo zachowuje się Option, a w wersji skrajnej Result<(), E>: „czy wszystko się udało”, bez żadnej wartości w środku. To zarazem powód, dla którego przypadkowe let _: () = it.collect(); kompiluje się i po cichu nie robi nic.
Na koniec dwie rzeczy, które dzieją się bezgłośnie. Zbieranie par do mapy nie jest scalaniem: [("Ada", 5), ("Ben", 3), ("Ada", 4)] daje {"Ada": 4, "Ben": 3}, bo późniejszy klucz nadpisuje wcześniejszy i nic o tym nie mówi — w Javie Collectors.toMap rzuca w tej sytuacji wyjątkiem, Rust wybrał ciszę, a gdy duplikaty mają się sumować, właściwym narzędziem jest fold z entry().or_insert(). Druga: wybór kolekcji jest deklaracją o twoim typie — HashSet wymaga Eq + Hash, BTreeSet wymaga Ord — a błąd o niespełnionej cesze (trait) pokaże się w linii z collect, nie przy definicji struktury. Dezorientuje to dokładnie raz.
Szukaj po polsku: wnioskowanie typów w Ruscie · adnotacja typu przy collect · rust E0282 type annotations needed · rust collect into Result short circuit · rust FromIterator impl for my type