Question
I have a Rust function that returns a Result:
fn find(id: &Id) -> Result<Item, ItemError> {
// ...
}
Then I use it like this:
let parent_items: Vec<Item> = parent_ids
.iter()
.map(|id| find(id).unwrap())
.collect();
How should I handle the case where one of the map iterations fails?
I know I could use flat_map, and in this case any errors would effectively be ignored:
let parent_items: Vec<Item> = parent_ids
.iter()
.flat_map(|id| find(id).into_iter())
.collect();
Because a Result iterator yields either 0 or 1 items depending on whether it is Err or Ok, flat_map would skip the error case.
However, I do not want to ignore errors. I want the whole operation to stop on the first failure and return a new error, or propagate the existing error.
What is the idiomatic way to do this in Rust?
Short Answer
By the end of this page, you will understand how to transform an iterator of Result values into a single Result in Rust, how collect() can stop on the first error, and when to use patterns like ?, map, and try_for_each for clean error handling.
Concept
In Rust, this pattern is about collecting fallible operations.
When you call a function like:
find(id) -> Result<Item, ItemError>
inside an iterator, each iteration produces a Result<Item, ItemError> instead of a plain Item.
That means this:
parent_ids.iter().map(find_like_function)
creates an iterator of this shape:
Iterator<Item = Result<Item, ItemError>>
The important idea is that Rust can collect this into:
Result<Vec<Item>, ItemError>
This is idiomatic Rust.
collect() knows how to do this because Result implements the necessary traits. It will:
- keep collecting
Ok(...)values into theVec - stop at the first
Err(...) - return that error immediately
This matters because many real programs perform repeated operations that can fail:
- loading records from IDs
- parsing multiple inputs
- validating request fields
- reading files
- calling external services
Rust encourages you to represent those failures explicitly instead of hiding them or panicking with unwrap().
So instead of forcing success with unwrap(), you usually let the iterator produce Results and then collect them into one final Result.
Mental Model
Think of an iterator of Results like a conveyor belt of boxes.
- An
Ok(item)box contains a real value. - An
Err(error)box contains a failure notice.
collect::<Result<Vec<_>, _>>() is like a worker loading items into a truck:
- if the next box contains an item, it gets loaded
- if the next box contains an error, loading stops immediately
- the worker reports the error instead of continuing
So the process is all-or-nothing:
- either you get a full
Vec<Item> - or you get the first error that occurred
That is why collect() is often the cleanest answer for this pattern in Rust.
Syntax and Examples
Core syntax
If your iterator produces Result<T, E>, you can collect it into Result<Vec<T>, E>:
let results: Result<Vec<T>, E> = items
.iter()
.map(|item| fallible_operation(item))
.collect();
Example using your case
fn load_parent_items(parent_ids: &[Id]) -> Result<Vec<Item>, ItemError> {
parent_ids
.iter()
.map(|id| find(id))
.collect()
}
This works because:
map(|id| find(id))producesResult<Item, ItemError>for eachidcollect()turns that intoResult<Vec<Item>, ItemError>
Propagating the error with ?
Usually you write it like this:
fn load_parent_items(parent_ids: &[Id]) -> Result<Vec<Item>, ItemError> {
let parent_items: Vec<Item> = parent_ids
.iter()
.map(|id| find(id))
.collect::<Result<Vec<_>, _>>()?;
Ok(parent_items)
}
The ? operator means:
- if collection succeeded, extract the
Vec<Item> - if it failed, return the
Errfrom the current function
Mapping to a new error type
If you want to return a different error type, use map_err:
fn load_parent_items(parent_ids: &[Id]) -> Result<Vec<Item>, MyError> {
parent_ids
.iter()
.map(|id| find(id).map_err(MyError::ItemLookupFailed))
.collect()
}
Here each ItemError is converted into MyError before collection.
Step by Step Execution
Consider this code:
fn parse_numbers(inputs: &[&str]) -> Result<Vec<i32>, std::num::ParseIntError> {
inputs
.iter()
.map(|s| s.parse::<i32>())
.collect()
}
Now imagine:
let inputs = ["10", "20", "abc", "40"];
What happens step by step
inputs.iter()creates an iterator over the strings..map(|s| s.parse::<i32>())transforms each string into aResult<i32, ParseIntError>.- The iterator now conceptually yields:
Ok(10)
Ok(20)
Err(...)
Ok(40)
collect()starts building aVec<i32>.- It sees
Ok(10)and stores10. - It sees
Ok(20)and stores20. - It sees
Err(...)and stops immediately. - It returns that error.
- The final
Ok(40)is never processed.
Result
Err(parse_error)
This short-circuiting behavior is exactly what you want when failure should stop the whole operation.
Real World Use Cases
Loading records from a database
You may have a list of IDs and need to load each corresponding record:
let users: Result<Vec<User>, DbError> = user_ids.iter().map(fetch_user).collect();
If one record fails to load, the whole operation returns an error.
Parsing user input
When converting many strings into numbers or dates, you often want all values to be valid:
let values: Result<Vec<i32>, _> = raw_values.iter().map(|s| s.parse()).collect();
Validating configuration
Applications often read multiple config fields and stop on the first invalid value.
Building API request objects
If each field transformation can fail, collecting Results keeps the code concise and safe.
File processing pipelines
When reading and transforming lines from a file, you may want to abort on the first malformed line instead of silently skipping it.
Real Codebase Usage
In real Rust codebases, developers commonly use this pattern in a few standard ways.
1. collect() for fallible mapping
This is the most common pattern when you want to transform many values and keep them all only if every step succeeds.
let items: Result<Vec<_>, _> = ids.iter().map(find).collect();
2. ? for early return
Functions that return Result often combine collect() with ?:
fn build(ids: &[Id]) -> Result<Vec<Item>, ItemError> {
let items = ids.iter().map(find).collect::<Result<Vec<_>, _>>()?;
Ok(items)
}
This avoids manual match blocks.
3. map_err for domain-specific errors
Real projects often have one top-level error type:
let items = ids
.iter()
.map(|id| find(id).map_err(AppError::from))
.collect::<Result<Vec<_>, _>>()?;
4. try_for_each when you do not need a collected Vec
If you only care about side effects and want to stop on error:
ids.iter().try_for_each(|id| {
let item = find(id)?;
println!("{:?}", item);
Ok::<(), ItemError>(())
})?;
5. Guarding against unwrap() in production code
In real codebases, unwrap() is usually avoided unless failure is impossible or truly unrecoverable. For recoverable operations, returning Result is preferred.
Common Mistakes
Using unwrap() on fallible operations
Broken code:
let parent_items: Vec<Item> = parent_ids
.iter()
.map(|id| find(id).unwrap())
.collect();
Problem:
- the program panics on error
- callers cannot handle the failure
Better:
let parent_items: Result<Vec<Item>, ItemError> = parent_ids
.iter()
.map(|id| find(id))
.collect();
Using flat_map and accidentally dropping errors
Broken idea:
let parent_items: Vec<Item> = parent_ids
.iter()
.flat_map(|id| find(id).into_iter())
.collect();
Problem:
Errvalues disappear- failures are silently ignored
Use this only when skipping errors is truly intended.
Expecting collect() to produce Vec<Result<T, E>>
This:
let x = parent_ids.iter().map(|id| find(id)).collect::<Vec<_>>();
produces:
Vec<Result<Item, ItemError>>
That means errors are still inside the vector. If you want short-circuiting behavior, collect into Result<Vec<_>, _> instead.
Forgetting the function must return Result
If you use ?, your surrounding function must also return a compatible Result.
Broken code:
fn load(ids: &[Id]) -> Vec<Item> {
ids.iter().map(find).collect::<Result<Vec<_>, _>>()?
}
Fix:
fn load(ids: &[Id]) -> Result<Vec<Item>, ItemError> {
ids.iter().map(find).collect()
}
Not converting error types when needed
If your function returns a different error type, convert it with map_err or From.
.map(|id| find(id).map_err(AppError::from))
Comparisons
Common approaches
| Approach | What it returns | Stops on error? | Keeps errors? | Typical use |
|---|---|---|---|---|
map(...).collect::<Result<Vec<_>, _>>() | Result<Vec<T>, E> | Yes | Yes | Best for collecting all successful values or returning first error |
map(...).collect::<Vec<_>>() | Vec<Result<T, E>> | No | Yes | Use when you want to inspect each result later |
| `flat_map( | x | result.into_iter()).collect::<Vec<_>>()` | Vec<T> | No |
try_for_each(...) | Result<(), E> | Yes | Yes | Best when you do not need to build a collection |
manual for loop with ? | Result<Vec<T>, E> | Yes | Yes | Good when logic is more complex |
collect() vs manual loop
A manual loop is sometimes clearer if you need extra logic:
fn load_parent_items(parent_ids: &[Id]) -> Result<Vec<Item>, ItemError> {
let mut items = Vec::new();
for id in parent_ids {
let item = find(id)?;
items.push(item);
}
Ok(items)
}
This is equivalent in behavior to the iterator version, but easier to extend with logging, conditions, or extra processing.
collect() vs try_for_each
- Use
collect()when you want a resulting collection. - Use
try_for_each()when you only want to perform actions and stop on failure.
Cheat Sheet
// Fallible mapping into a Vec
let items: Result<Vec<_>, _> = ids.iter().map(|id| find(id)).collect();
// Propagate error
let items = ids.iter().map(|id| find(id)).collect::<Result<Vec<_>, _>>()?;
// Convert error type first
let items = ids
.iter()
.map(|id| find(id).map_err(AppError::from))
.collect::<Result<Vec<_>, _>>()?;
// Side effects only
ids.iter().try_for_each(|id| {
let item = find(id)?;
println!("{:?}", item);
Ok::<(), ItemError>(())
})?;
Rules to remember
map()does not handle errors by itself.unwrap()panics; it does not return an error.collect::<Result<Vec<_>, _>>()stops on the firstErr.flat_map(...into_iter())onResultdrops errors.- Use
?to return errors early from the surrounding function. - If needed, convert errors with
map_err.
Type pattern
Iterator<Item = Result<T, E>>
-> collect()
Result<Vec<T>, E>
FAQ
How do I stop iteration on the first error in Rust?
Use an iterator that produces Result<T, E> and collect it into Result<Vec<T>, E>:
let items: Result<Vec<_>, _> = ids.iter().map(find).collect();
collect() stops at the first Err.
Does map() itself stop on Err?
No. map() only transforms items. The short-circuiting happens when you use consumers like collect() into a Result, or methods like try_for_each().
Why is unwrap() a bad choice here?
Because unwrap() panics if an error occurs. That crashes the current flow instead of letting you return a controlled error to the caller.
Can I return a different error type?
Yes. Use map_err() inside the iterator:
.map(|id| find(id).map_err(AppError::LookupFailed))
When should I use try_for_each() instead of collect()?
Use try_for_each() when you do not need a Vec of results and only want to perform work until an error occurs.
What if I want to keep all errors instead of stopping at the first one?
Then collect::<Result<Vec<_>, _>>() is not the right tool. You would usually collect into Vec<Result<T, E>> and process the errors afterward.
Is a manual for loop less idiomatic than collect()?
No. Both are idiomatic. Use collect() for concise transformations and a for loop when the logic is easier to read that way.
Mini Project
Description
Build a small Rust function that loads product prices from a list of string inputs. Each string should be parsed into a number. If any input is invalid, the function should stop immediately and return the parsing error. This demonstrates how collect() can turn an iterator of Result values into a single Result<Vec<_>, _>.
Goal
Create a function that converts a list of strings into a Vec<i32> and returns an error if any string cannot be parsed.
Requirements
- Write a function that accepts a slice of string slices (
&[&str]). - Parse each string into an
i32. - Return
Result<Vec<i32>, std::num::ParseIntError>. - Stop on the first invalid number.
- Show at least one successful call and one failing call.
Keep learning
Related questions
Absolute Values in Rust: Using the abs() Method
Learn how to calculate absolute values in modern Rust with abs(), handle signed integer overflow, and choose safer alternatives.
Accepting Iterators in Rust Functions with impl Iterator
Learn how to write Rust functions that accept HashMap iterators using generic bounds or impl Iterator, avoiding trait object and lifetime errors.
Access Enum Variant Values in Rust with Pattern Matching
Learn how to extract and print values stored in Rust enum variants using match and if let pattern matching, with practical examples.