Iterators, Closures, and Loops
Rule
Use iterator pipelines for simple transforms and loops for branching, mutation, early exits, or multi-step state.
Why
Pipelines are concise when each operation names a transformation; loops expose control flow when work is stateful or conditional.
Do
- Choose
iter,iter_mut, orinto_iterfrom the intended ownership. - Collect when the destination type is clear; annotate ambiguous results.
- Collect fallible maps into
Result<Collection, _>for simple fail-fast transformation. - Use
try_foldortry_for_eachonly for short readable fallible accumulation. - Use loops for branches, mutation, multiple accumulators, or nontrivial errors.
- Keep closures short and extract named helpers for reusable or behavior-heavy logic.
- Use
movewhen a closure outlives its scope or crosses a task/thread boundary. - Clone into closures when it avoids awkward lifetimes and cost is acceptable.
- Use
enumerateorzipinstead of manual paired indexes.
Avoid
- Do not hide control flow in long pipelines.
- Do not use
for_eachfor side-effect-heavy work. - Do not use
foldfor a complex mutable accumulator. - Do not collect a temporary collection only to iterate once.
- Do not hide logging, metrics, I/O, or mutation in transformation closures.
Example
#![allow(unused)]
fn main() {
pub fn active_names(runs: &[Run]) -> Vec<String> {
runs.iter()
.filter(|run| run.is_active())
.map(|run| run.name().to_owned())
.collect()
}
pub fn first_failed(runs: &[Run]) -> Result<Option<RunId>, Error> {
for run in runs {
if run.is_finished() && !run.exit_status()?.success() {
return Ok(Some(run.id()));
}
}
Ok(None)
}
}
Exceptions
- Follow configured Clippy policy when it prefers an equally clear local form.
- Use
for_eachin a fluent API only when side effects remain obvious.