Python vs Rust Loops
Iteration styles, iterator adapters, and the functional pipeline approach that Rust favors over Python comprehensions.
For Loops
Both languages use for...in syntax, which is unusual enough to note -- most C-family languages use index-based for loops, but Python and Rust both iterate over values directly. Python's for x in items: and Rust's for x in items {} look nearly identical, and the behavior aligns: each iteration binds x to the next value. The difference hides in ownership. Python objects are reference-counted, so iterating over a list does not consume it. Rust's iterator consumes the source by default: for x in vec moves the vector, and you cannot use vec afterward. To iterate without consuming, you iterate over a reference: for x in &vec (immutable borrow) or for x in &mut vec (mutable borrow). This three-way choice (move, borrow, mutable borrow) has no Python analogue. Python developers can start by always using &vec and treating it as a read-only iteration, which matches Python's behavior. The explicit borrow syntax feels verbose at first, but it documents intent: anyone reading for x in &mut items knows the loop body mutates elements.
Iterate over a collection
colors = ["red", "green", "blue"]
for color in colors:
print(color)
# colors still usablelet colors = vec!["red", "green", "blue"];
for color in &colors {
println!("{color}");
}
// colors still usable (borrowed)Range-based loop
for i in range(5):
print(i) # 0..4for i in 0..5 {
println!("{i}"); // 0..4
}Enumerate and Zip
Python's enumerate() returns (index, value) pairs and is one of the first standard library functions new Python developers learn. Rust has .enumerate() as a method on iterators, producing (usize, T) tuples. The syntax difference is minor: Python wraps the iterable (enumerate(items)), while Rust chains the method (items.iter().enumerate()). zip() also exists in both ecosystems with matching semantics -- it pairs elements from two iterables and stops at the shorter one. Python's itertools.zip_longest fills missing values with a default; Rust's equivalent is the itertools crate's zip_longest or the standard library's .zip() combined with .chain(). The pattern that trips Python developers: Rust's .enumerate() returns a tuple where the index is the first element, matching Python, but destructuring syntax differs. Python uses for i, item in enumerate(items):, while Rust uses for (i, item) in items.iter().enumerate() {}. Parentheses around the destructured pattern are mandatory in Rust because tuples use parentheses, while Python allows either form. Both communities consider enumerate preferable to manual index tracking.
Enumerate
for i, color in enumerate(colors):
print(f"{i}: {color}")for (i, color) in colors.iter().enumerate() {
println!("{i}: {color}");
}Zip two collections
names = ["Alice", "Bob"]
scores = [95, 87]
for name, score in zip(names, scores):
print(f"{name}: {score}")let names = ["Alice", "Bob"];
let scores = [95, 87];
for (name, score) in names.iter().zip(scores.iter()) {
println!("{name}: {score}");
}List Comprehension vs Iterator Chains
Python developers reach for list comprehensions instinctively: [x * 2 for x in nums if x > 0]. Rust has no comprehension syntax. The equivalent is an iterator chain: nums.iter().filter(|&&x| x > 0).map(|&x| x * 2).collect::<Vec<_>>(). The chain reads in processing order (filter, then map, then collect), while comprehensions read in generation order (source, transform, filter). Neither ordering is universally clearer; preference tends to align with whichever language you learned first. The collect::<Vec<_>>() call at the end is unfamiliar to Python developers. Rust iterators are lazy -- .filter() and .map() produce new iterator adapters without doing any work. collect() drives the chain and assembles the results into a concrete collection. The turbofish (::<Vec<_>>) tells the compiler what collection type to produce, since Rust iterators can collect into Vec, HashSet, HashMap, String, and other types. Python's comprehensions always produce a list (or dict/set with the appropriate brackets), so the collection type is implicit. Rust's explicitness adds verbosity but also flexibility -- the same iterator chain can produce different collection types by changing the turbofish annotation.
Filter and transform
nums = [1, -2, 3, -4, 5]
result = [n * 2 for n in nums if n > 0]
# => [2, 6, 10]let nums = vec![1, -2, 3, -4, 5];
let result: Vec<i32> = nums.iter()
.filter(|&&n| n > 0)
.map(|&n| n * 2)
.collect();
// => [2, 6, 10]Collect into different types
words = ["hello", "world", "hello"]
unique = set(words)
# => {"hello", "world"}use std::collections::HashSet;
let words = vec!["hello", "world", "hello"];
let unique: HashSet<_> = words.into_iter().collect();
// => {"hello", "world"}Loop Control and Labeled Breaks
Python and Rust share break and continue with identical semantics for single-level loops. The divergence appears with nested loops. Python has no mechanism for breaking out of an outer loop from an inner one -- developers use flag variables, extract the nested loop into a function, or restructure the logic. Rust supports labeled breaks: 'outer: for ... { for ... { break 'outer; } }. The label syntax uses a leading apostrophe (a lifetime-like annotation) and lets you target any enclosing loop by name. Rust also has a loop {} construct -- an infinite loop that you exit with break. This is distinct from while true {} because the compiler knows that loop {} always runs at least once, enabling better flow analysis. break inside a loop {} can return a value: let result = loop { break 42; }. Python has no value-returning break; you assign to a variable before breaking. The loop-as-expression pattern is useful for retry logic and input validation, where you want to run code until a condition is met and then use the result. It is one of the places where Rust's "everything is an expression" philosophy shows clear ergonomic benefit.
Labeled break from nested loop
# No labeled break in Python
found = False
for row in matrix:
for cell in row:
if cell == target:
found = True
break
if found:
break'outer: for row in &matrix {
for cell in row {
if *cell == target {
break 'outer;
}
}
}Value-returning loop
while True:
line = input("> ")
if line.strip():
result = line.strip()
breaklet result = loop {
let line = read_input();
let trimmed = line.trim();
if !trimmed.is_empty() {
break trimmed.to_string();
}
};