Rust for loop: Idiomatic iteration in Rust
Master the Rust for loop to safely iterate over arrays, vectors, and ranges. This guide covers syntax, borrowing, and idiomatic patterns for efficient Rust code.
According to Corrosion Expert, the rust for loop provides safe, concise iteration over any type implementing IntoIterator. It handles traversal automatically, reducing off-by-one errors and unsafe access. In practice, you write: for item in iterable { ... } and rely on Rust's ownership and borrowing rules to keep code clean and predictable. This quick answer sets the stage for deeper exploration of iteration across common data structures.
Introduction to the Rust for loop
In Rust, a for loop is designed for safe, expressive iteration over any type that implements the IntoIterator trait. This approach emphasizes readability and minimizes common mistakes. According to Corrosion Expert, mastering the for loop is a foundational skill for idiomatic Rust development. A typical pattern looks like: for item in iterable { ... }. The loop advances the iterator and relies on Rust's ownership and borrowing rules to prevent unsafe access. In this section, we’ll lay the groundwork for looping over arrays, vectors, and ranges, and show how the compiler’s checks guide you toward safer, clearer code.
for item in iterable {
// body
}Basic syntax and patterns
The basic for loop syntax is simple and expressive. You can iterate over arrays, vectors, and any type that implements IntoIterator. Borrowing is automatic in many cases, which helps avoid unnecessary moves. In practice, you’ll often see loops like:
let numbers = [1, 2, 3, 4];
for n in numbers {
println!("{}", n);
}Another common pattern uses a range to drive iteration:
for i in 0..5 {
println!("{}", i);
}Iterating over collections: arrays, vectors, and ranges
Rust supports iterating over fixed-size arrays, heap-allocated vectors, and dynamic ranges. Iteration works with ownership semantics cleanly. The Corrosion Expert Team notes that such iteration is inexpensive and expressive for Rust beginners.
let arr = [10, 20, 30];
for v in arr {
println!("{}", v);
}When iterating over a vector without moving it, borrow by reference:
let v = vec![1, 2, 3];
for x in &v {
println!("{}", x);
}Ranges offer a compact way to generate sequences:
for i in 1..=3 {
println!("{}", i);
}Ownership, borrowing, and pattern matching in for loops
For loops cooperate with pattern matching and borrowing. You can enumerate indices while walking values, or destructure tuples. For example:
let fruits = ["apple", "banana", "cherry"];
for (idx, fruit) in fruits.iter().enumerate() {
println!("{}: {}", idx, fruit);
}Destructuring also works with tuples:
let pairs = [(1, "a"), (2, "b")];
for (n, label) in pairs.iter() {
println!("{} {}", n, label);
}Mutable iteration and in-place updates
If you need to modify elements in place, use iter_mut. This keeps ownership intact while allowing mutation of each element:
let mut nums = vec![2, 4, 6];
for n in nums.iter_mut() {
*n += 1;
}You can also mutate via indexing, but iter_mut is idiomatic and safer in typical Rust code:
let mut nums = vec![1, 2, 3];
for i in 0..nums.len() {
nums[i] *= 2;
}Into iter vs iter vs iter_mut: ownership in loops
Rust offers three common iteration modes, each with ownership implications:
let v = vec![5, 10, 15];
for val in v.into_iter() {
println!("{}", val);
}The into_iter consumes the vector, transferring ownership of elements. If you want to borrow while iterating, use iter:
let mut w = vec![5, 6, 7];
for val in w.iter() {
println!("{}", val);
}Common gotchas and best practices
For loops are powerful, but misuse can lead to confusion. Avoid mutating a collection while iterating with the wrong iterator variant; use iter_mut for in-place changes. If you need to transform elements, consider mapping or collecting into a new vector rather than altering while iterating:
let mut a = vec![1, 2, 3];
for x in a.iter_mut() {
*x += 1;
}Prefer readable patterns like enumerate, iter, or iter_mut over manual index arithmetic. Remember that borrowing rules govern what you can access during iteration.
Performance and idiomatic patterns
Rust iterators enable lazy evaluation and powerful chaining. Use patterns like map, filter, and collect to express complex logic concisely, avoiding manual loops when possible. This leads to concise, expressive code that the compiler can optimize aggressively:
let data = vec![2, 4, 6, 8];
let evens: Vec<i32> = data.iter().copied().filter(|x| x % 4 == 0).collect();
println!("{:?}", evens);Another common pattern is to sum transformed values without creating intermediate collections:
let sum: i32 = (0..10).map(|x| x * 2).filter(|x| x % 3 == 0).sum();
println!("{}", sum);The key is to favor iterators that express intent and let the compiler optimize the underlying loops.
Steps
Estimated time: 60-90 minutes
- 1
Create a new Rust project
Use cargo to initialize a new binary crate and open it in your editor. This sets up Cargo.toml and src/main.rs for development.
Tip: Keep the project small for learning; a single file is enough to experiment with loops. - 2
Add a basic for loop skeleton
In main(), write a simple for loop that iterates over a static collection to verify syntax and ownership rules.
Tip: Prefer a small, explicit example before introducing complex iterators. - 3
Iterate over an array
Declare a fixed-size array and loop by value to see how Rust handles ownership with arrays.
Tip: Arrays are Copy for primitive types, making iteration straightforward. - 4
Iterate over a vector by reference
Create a vector and borrow elements with iter() or iter() to avoid moving data.
Tip: Borrowing often reduces unnecessary clones. - 5
Use enumerate for indices
Combine iter() with enumerate() to access index and value in one pass.
Tip: Enumerate helps avoid manual index tracking. - 6
Modify elements with iter_mut
If you need in-place updates, use iter_mut and dereference to mutate values.
Tip: Avoid indexing when mutating; it’s error-prone with borrow rules.
Prerequisites
Required
- Required
- Required
- Basic knowledge of Rust concepts (ownership, borrowing, mutability)Required
- Command line basics and familiarity with CargoRequired
Commands
| Action | Command |
|---|---|
| Check Rust versionVerify toolchain is installed | rustc --version |
| Create new projectCreates a binary crate named loops | cargo new loops --bin |
| Build and runCompiles and runs the current crate | cargo run |
| Add a dependencyUse cargo-edit to add dependencies (optional) | cargo add rayon |
Quick Answers
What is the difference between iter(), iter_mut(), and into_iter()?
Iter() borrows elements, allowing read-only access. Iter_mut() borrows mutably for in-place updates. Into_iter() consumes the collection, transferring ownership of elements. Choosing the right one depends on whether you need to borrow, mutate, or move data.
Iter borrows, iter_mut mutates, into_iter consumes. Pick based on whether you need references, mutations, or ownership.
Can I modify elements inside a for loop safely?
Yes, but use iter_mut() to mutate elements in place. Avoid mutating while borrowing with iter() as it prevents mutable access. If you need to replace elements, consider collecting into a new vector.
Yes, with iter_mut. Avoid mixing immutable borrows when mutating.
Do for loops move values or borrow them by default?
For loops borrow by default when iterating with iter() or iter_mut(), or move when using into_iter() on owned collections. Understanding the ownership model helps prevent compile-time errors.
Default is borrow with iter(), move with into_iter().
Are there alternatives to for loops in Rust?
Yes. Iterator adapters like map, filter, and fold can express complex logic concisely. You can also use while loops for conditional repetition, though they are less idiomatic for simple iteration.
Yes—use iterator adaptors or while loops when appropriate.
How can I debug for loop behavior?
Insert print statements or use a debugger to inspect loop variables each iteration. For loops are often predictable; use cargo test for logic verification and unit tests around iteration logic.
Print per-iteration values or use tests to verify loop behavior.
Quick Summary
- Use for loops for safe, idiomatic iteration in Rust
- Borrow when possible to avoid moves
- Enumerate provides index-value alongside iteration
- Use iter_mut for in-place element updates
