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
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>
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)
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.
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).())
.();
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 |
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.- panics; it does not return an error.
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 inside the iterator:
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
Accessing Cargo Package Metadata in Rust
Learn how to read Cargo package metadata like version, name, and authors in Rust using compile-time environment macros.
Default Function Arguments in Rust: What to Use Instead
Learn how Rust handles default function arguments, why they are not supported, and practical patterns to achieve similar behavior.
Fixing Rust "linker 'cc' not found" on Debian in WSL
Learn why Rust shows "linker 'cc' not found" on Debian in WSL and how to fix it by installing the required C build tools.