Rust 06. Generics, Error Handling, Closures, and Iterators
Summary
Once you get a little more comfortable with Rust, four ideas start showing up together: reusing logic across types, handling failure without hiding it, treating small pieces of logic like values, and processing collections in clear transformation steps. Those ideas are represented by generics, error handling, closures, and iterators.
This post connects those four topics inside one Cargo-based workflow. In short, generics generalize over types, Result and ? propagate failure, closures capture surrounding values in short inline logic, and iterators provide a composable and lazy model for collection processing.
Document Information
- Written on: 2026-04-13
- Verification date: 2026-04-16
- Document type: tutorial
- Test environment: Windows 11 Pro, Cargo project, Windows PowerShell example commands,
src/main.rs - Test version: rustc 1.94.0, cargo 1.94.0
- Source quality: only official documentation is used.
- Note: representative examples were rerun locally, and advanced trait bounds or production-oriented error design are intentionally out of scope.
Problem Definition
At the beginner stage, the following four questions often appear together even though they first look like unrelated syntax.
- how to reuse one piece of logic across multiple types
- how to expose recoverable failure in a return value
- how to pass short logic around while still reading outer values
- how to express collection processing as a chain of steps instead of manual loop state
This post connects those questions at the beginner level. It does not cover lifetime-heavy generic design, custom error architecture, the full iterator adaptor space, or async streams.
Verified Facts
- Generic type parameters are Rust’s basic tool for reducing duplication across types. Evidence: Generic Data Types
- Recoverable errors are typically expressed with
Result<T, E>, and the?operator shortens propagation inside compatible return types. Evidence: Recoverable Errors with Result - A closure is an anonymous function that can capture values from its surrounding environment. Evidence: Closures
- Iterator adapters such as
mapandfilterare usually lazy, and work happens when the iterator is consumed by something likesum,collect, or aforloop. Evidence: Processing a Series of Items with Iterators - The simplest beginner practice flow is still a small
cargo newproject. Evidence: Hello, Cargo!
Directly Confirmed Results
1. A single practice project made repeated reruns easy
- Direct result: creating one fresh Cargo project and replacing
src/main.rswhile rerunningcargo runwas the simplest practice loop.
cargo new rust-generics-errors-closures
cd rust-generics-errors-closures
code .
cargo run
2. Generics and Result showed reuse and failure handling as separate concerns
- Direct result: the example below made it easy to see generic reuse and recoverable failure in the same file.
fn largest<T: PartialOrd + Copy>(list: &[T]) -> T {
let mut largest = list[0];
for &item in list {
if item > largest {
largest = item;
}
}
largest
}
fn safe_divide(a: f64, b: f64) -> Result<f64, String> {
if b == 0.0 {
Err(String::from("Cannot divide by zero."))
} else {
Ok(a / b)
}
}
fn main() {
let numbers = [10, 40, 20, 30];
println!("largest number = {}", largest(&numbers));
match safe_divide(10.0, 0.0) {
Ok(value) => println!("result = {}", value),
Err(message) => println!("error = {}", message),
}
}
- Observed result:
largest number = 40
error = Cannot divide by zero.
3. Closures and iterators worked well as one data-processing flow
- Direct result: the example below showed environment capture by a closure and a small iterator pipeline in one place.
fn main() {
let bonus = 5;
let add_bonus = |score: i32| score + bonus;
println!("closure result = {}", add_bonus(10));
let total: i32 = vec![1, 2, 3, 4, 5]
.iter()
.copied()
.filter(|n| n % 2 == 0)
.map(|n| n * 2)
.sum();
println!("total = {}", total);
}
- Observed result:
closure result = 15
total = 12
4. The combined example showed why these four ideas often appear together
- Direct result: the example below connected generics,
Result, closures, and iterators in one flow.
use std::num::ParseIntError;
use std::str::FromStr;
fn parse_values<T>(inputs: &[&str]) -> Result<Vec<T>, T::Err>
where
T: FromStr,
{
inputs.iter().map(|input| input.parse::<T>()).collect()
}
fn main() -> Result<(), ParseIntError> {
let inputs = vec!["10", "20", "30"];
let numbers = parse_values::<i32>(&inputs)?;
let doubled_total: i32 = numbers.iter().map(|n| (n + 3) * 2).sum();
println!("numbers = {:?}", numbers);
println!("doubled_total = {}", doubled_total);
Ok(())
}
- Observed result:
numbers = [10, 20, 30]
doubled_total = 138
Interpretation / Opinion
- Interpretation: generics,
Result, closures, and iterators usually appear in real Rust code as one flow of reading, transforming, and safely returning data, not as isolated syntax features. - Opinion: for beginners, iterators are easier to read when introduced as visible data-processing pipelines rather than as just a different way to write loops.
- Opinion: the
?operator becomes much easier to use later in file I/O or parsing code once it is understood first as control flow for early error return.
Limits and Exceptions
- This post stays at the beginner-summary level. Lifetime-heavy generics, advanced trait bounds, custom error types, and the broader iterator adaptor set are outside the scope.
- The differences between
Fn,FnMut, andFnOnce, along with deeper borrow behavior around closures, are not covered here. - Lazy iterator behavior matters, but this post only touches the basic consumption points such as
sum()andcollect(). - Exact output and diagnostic wording can vary by Rust version, and this post does not compare macOS, Linux, or WSL-specific behavior.
댓글남기기