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.

Corrosion Expert
Corrosion Expert Team
·5 min read
Quick AnswerDefinition

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.

Rust
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:

Rust
let numbers = [1, 2, 3, 4]; for n in numbers { println!("{}", n); }

Another common pattern uses a range to drive iteration:

Rust
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.

Rust
let arr = [10, 20, 30]; for v in arr { println!("{}", v); }

When iterating over a vector without moving it, borrow by reference:

Rust
let v = vec![1, 2, 3]; for x in &v { println!("{}", x); }

Ranges offer a compact way to generate sequences:

Rust
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:

Rust
let fruits = ["apple", "banana", "cherry"]; for (idx, fruit) in fruits.iter().enumerate() { println!("{}: {}", idx, fruit); }

Destructuring also works with tuples:

Rust
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:

Rust
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:

Rust
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:

Rust
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:

Rust
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:

Rust
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:

Rust
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:

Rust
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. 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. 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. 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. 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. 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. 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.
Pro Tip: Prefer iterators and for loops over manual index-based loops for clarity and safety.
Warning: Do not mutate a collection while iterating with the wrong iterator variant; use iter_mut for in-place changes.
Note: Use enumerate() to access both index and value without keeping a separate counter.
Pro Tip: Leverage iterator chaining (map, filter, collect) for concise, lazy computations.
Warning: Be mindful of ownership: into_iter consumes the collection, while iter borrows and iter_mut mutates.

Prerequisites

Required

Commands

ActionCommand
Check Rust versionVerify toolchain is installedrustc --version
Create new projectCreates a binary crate named loopscargo new loops --bin
Build and runCompiles and runs the current cratecargo 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