In this article, we’ll explore Iterators and Closures in Rust and compare them to Python’s generators and lambda functions. While both languages offer ways to work with lazily evaluated data and anonymous functions, Rust’s iterator and closure mechanisms provide more control, efficiency, and safety.
In Python, iterators are objects that can be iterated (looped) over, such as lists, dictionaries, or ranges. You can create custom iterators using generators, which lazily yield values on demand.
Generators in Python are defined with the yield
keyword and can be used to lazily produce a sequence of values:
def countdown(n):
while n > 0:
yield n
n -= 1
for num in countdown(5):
print(num)
range()
, enumerate()
, and zip()
, which generate values on demand.In Rust, iterators are a core feature of the language, with many iterator methods provided by the standard library. Iterators in Rust are lazy by default, meaning they only compute values when they are needed, much like Python generators.
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
for num in numbers.iter() {
println!("{}", num);
}
}
In Rust, you can also implement your own iterator by defining the Iterator
trait:
struct Countdown {
count: i32,
}
impl Iterator for Countdown {
type Item = i32;
fn next(&mut self) -> Option<Self::Item> {
if self.count > 0 {
self.count -= 1;
Some(self.count + 1)
} else {
None
}
}
}
fn main() {
let countdown = Countdown { count: 5 };
for num in countdown {
println!("{}", num);
}
}
Iterator
trait, which requires implementing the next
method. This makes iterators in Rust more powerful and flexible than Python’s generators..collect()
)..map()
, .filter()
, and .collect()
, which are evaluated lazily, similar to Python’s generator expressions.Rust provides a rich set of iterator adaptors, which are methods that allow you to transform iterators into new iterators. These can be used to apply operations like filtering, mapping, or summing over elements without needing to manually write loops.
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
let squared: Vec<i32> = numbers.iter().map(|x| x * x).collect();
println!("{:?}", squared);
}
In this example, the .map()
method transforms each value, and .collect()
turns the iterator into a Vec
.
In Python, you might use a list comprehension to achieve a similar result:
numbers = [1, 2, 3, 4, 5]
squared = [x * x for x in numbers]
print(squared)
In Python, closures are functions defined within another function that capture variables from their enclosing scope. Python also supports lambda functions, which are anonymous, single-expression functions.
def outer(x):
def inner(y):
return x + y
return inner
closure = outer(10)
print(closure(5)) # Output: 15
add = lambda x, y: x + y
print(add(5, 3)) # Output: 8
Rust also supports closures, which are anonymous functions that can capture values from their environment. Closures in Rust are more flexible than Python’s lambdas and can capture variables by value, reference, or mutable reference, depending on the situation.
fn main() {
let x = 10;
let add = |y| x + y;
println!("{}", add(5)); // Output: 15
}
In this example, the closure captures x
from its environment. Closures in Rust automatically infer whether to borrow or move captured variables based on how they are used.
fn main() {
let mut x = 10;
let mut add = |y| {
x += y;
x
};
println!("{}", add(5)); // Output: 15
println!("{}", x); // Output: 15
}
In this example, the closure captures x
mutably, allowing it to modify the value.
In Rust, closures are commonly passed as function parameters. This is a key feature that allows Rust to use closures in many iterator adaptors like map
, filter
, and for_each
.
fn apply<F>(f: F)
where F: Fn(i32) -> i32 {
let result = f(10);
println!("{}", result);
}
fn main() {
let closure = |x| x + 5;
apply(closure); // Output: 15
}
In this example, apply
takes a closure that conforms to the Fn
trait, applies it to the value 10, and prints the result.
def apply(func):
result = func(10)
print(result)
apply(lambda x: x + 5) # Output: 15
Fn
, FnMut
, or FnOnce
), depending on how they capture variables. This adds flexibility in how closures are used, allowing you to choose whether closures can modify their environment.Rust’s iterators and closures provide more control and type safety than Python’s generators and lambda functions. While Python’s approach is more dynamic and flexible, Rust’s stricter memory management and trait-based system offer more powerful guarantees, especially when working with complex data transformations or performance-critical code.
In the next article, we’ll dive into Rust’s Traits and compare them to Python’s Duck Typing and Protocols. Stay tuned!
fn main() {
println!("1. Simple Iterator:");
simple_iterator();
println!("\n2. Custom Iterator:");
custom_iterator();
println!("\n3. Iterator Adaptor:");
iterator_adaptor();
println!("\n4. Simple Closure:");
simple_closure();
println!("\n5. Mutable Closure:");
mutable_closure();
println!("\n6. Closure as Function Parameter:");
closure_as_parameter();
}
fn simple_iterator() {
let numbers = vec![1, 2, 3, 4, 5];
for num in numbers.iter() {
println!("{}", num);
}
}
struct Countdown {
count: i32,
}
impl Iterator for Countdown {
type Item = i32;
fn next(&mut self) -> Option<Self::Item> {
if self.count > 0 {
self.count -= 1;
Some(self.count + 1)
} else {
None
}
}
}
fn custom_iterator() {
let countdown = Countdown { count: 5 };
for num in countdown {
println!("{}", num);
}
}
fn iterator_adaptor() {
let numbers = vec![1, 2, 3, 4, 5];
let squared: Vec<i32> = numbers.iter().map(|x| x * x).collect();
println!("{:?}", squared);
}
fn simple_closure() {
let x = 10;
let add = |y| x + y;
println!("{}", add(5)); // Output: 15
}
fn mutable_closure() {
let mut x = 10;
let mut add = |y| {
x += y;
x
};
println!("{}", add(5)); // Output: 15
println!("{}", x); // Output: 15
}
fn apply<F>(f: F)
where F: Fn(i32) -> i32 {
let result = f(10);
println!("{}", result);
}
fn closure_as_parameter() {
let closure = |x| x + 5;
apply(closure); // Output: 15
}
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.
000 - Learning Rust as a Pythonista: A Suggested Path
001 - Learning Rust as a Pythonista: How to Create and Run a Rust File
002 - Learning Rust as a Pythonista: Basic Syntax and Structure
006 - Rust Traits vs. Python Duck Typing: A Comparison for Pythonistas
007 - Concurrency in Rust for Python Developers