2024-10-08
006 - Iterators and Closures
Learning Rust as a Pythonista: Iterators and Closures
Iterators and closures are where Rust starts to feel concise and expressive, not just strict.
Why this matters for Python developers
- Python generators/comprehensions map nicely to Rust iterator chains.
- Rust closures are similar to lambdas but interact with ownership/borrowing.
- Understanding
itervsinto_itervsiter_mutprevents many compiler errors.
Learning goals
By the end of this lesson, you should be able to:
- Choose correctly between
iter,into_iter, anditer_mut. - Build lazy pipelines with
map,filter, andfold. - Understand closure capture behavior (
Fn,FnMut,FnOnce).
Concepts in 5 minutes
- Iterators are lazy until consumed (
collect,sum,for_each, etc.). iter()borrows items,into_iter()consumes ownership,iter_mut()mutably borrows items.- Closures capture environment by reference/mutable reference/value based on use.
Python baseline snippet
nums = [1, 2, 3, 4, 5]
result = sum(x * x for x in nums if x % 2 == 1)
print(result)
Rust equivalent snippets
let nums = vec![1, 2, 3, 4, 5];
let sum_of_odd_squares: i32 = nums
.iter()
.filter(|x| **x % 2 == 1)
.map(|x| x * x)
.sum();
let mut values = vec![1, 2, 3];
for v in values.iter_mut() {
*v *= 10;
}
One runnable end-to-end example
fn apply_twice<F>(mut f: F, value: i32) -> i32
where
F: FnMut(i32) -> i32,
{
let first = f(value);
f(first)
}
fn main() {
let nums = vec![1, 2, 3, 4, 5, 6];
// 1) Borrowed iteration (iter)
let odd_squares_sum: i32 = nums
.iter()
.filter(|x| **x % 2 == 1)
.map(|x| x * x)
.fold(0, |acc, x| acc + x);
println!("odd_squares_sum = {}", odd_squares_sum);
// 2) Consuming iteration (into_iter)
let owned_strings = vec!["rust", "python", "zola"];
let upper: Vec<String> = owned_strings
.into_iter()
.map(|s| s.to_uppercase())
.collect();
println!("upper = {:?}", upper);
// 3) Mutable iteration (iter_mut)
let mut scores = vec![10, 20, 30];
for score in scores.iter_mut() {
*score += 5;
}
println!("scores = {:?}", scores);
// 4) Closure capture + FnMut
let mut offset = 1;
let add_offset = |x| {
offset += 1;
x + offset
};
let result = apply_twice(add_offset, 10);
println!("apply_twice result = {}", result);
}
Common mistakes
- Using
into_iter()and then trying to reuse the original collection. - Forgetting dereference in iterator closures (
|x| **xwhen needed). - Choosing
Fnwhen closure actually mutates captured state (FnMut).
Quick practice
- Transform
Vec<&str>intoVec<String>usinginto_iter(). - Compute product of even numbers with
filter+fold.
Recap
Iterators and closures are idiomatic Rust power tools. Once ownership semantics are clear, these patterns become both ergonomic and fast.
Next lesson: traits vs duck typing and protocols.
Join the Journey Ahead!
If you're eager to continue this learning journey and stay updated with the latest insights, consider subscribing. By joining our mailing list, you'll receive notifications about new articles, tips, and resources to help you seamlessly pick up Rust by leveraging your Python skills.
Other articles in the series
- 000 - Learning Rust as a Pythonista: A Suggested Path
- 001 - Your First Rust Program
- 002 - Basic Syntax and Structure
- 003 - Ownership, Borrowing, and Lifetimes
- 004 - Error Handling
- 005 - Structs and Enums
- 007 - Traits vs Duck Typing and Protocols
- 008 - Concurrency in Rust for Python Developers
- 009 - Async Concurrency with Tokio
- 010 - Pattern Matching in Rust
- 011 - Macros in Rust