5/5
## The Challenge: Looping Over a Collection Multiple Times in Rust When working with collections in Rust, a common task is to iterate over their elements. The idiomatic `for` loop syntax, `for v in collection`, is straightforward but can lead to unexpected behavior if you need to access the collection after the loop. Consider this scenario: ```rust // iter.rs #![allow(unused)] fn main() { let vals: Vec<u32> = vec![1, 2, 3, 4, 5]; for v in vals { // First loop // Process each value v } // Attempting a second loop: // for v in vals { // This would cause a compile error // // Process each value v again // } } ``` If you uncomment the second `for` loop and try to compile this code, the Rust compiler will prevent it, issuing an error: ``` error[E0382]: use of moved value: `vals` --> examples/iter.rs:11:15 | 5 | let vals: Vec<u32> = vec![1, 2, 3, 4, 5]; | ---- move occurs because `vals` has type `Vec<u32>`, which does not implement the `Copy` trait ... 7 | for v in vals { | ---- `vals` moved due to this implicit call to `.into_iter()` ... 11 | for v in vals { | ^^^^ value used here after move | note: `into_iter` takes ownership of the receiver `self`, which moves `vals` --> /home/t4sk/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/iter/traits/collect.rs:313:18 | 313| fn into_iter(self) -> Self::IntoIter; | ---- help: consider iterating over a slice of the `Vec<u32>`'s content to avoid moving into the `for` loop | 7 | for v in &vals { | + ``` The error message `error[E0382]: use of moved value: 'vals'` clearly indicates that `vals` is no longer available for the second loop. This is because the first loop *consumed* the vector. The reason for this consumption lies in Rust's ownership system and how `for` loops interact with iterators. ## Understanding Rust's Iterators and Ownership: The Role of `into_iter()` The `for v in collection` syntax in Rust is syntactic sugar. Behind the scenes, it calls a method on the collection to get an iterator. Specifically, for a collection like `Vec<T>`, this loop: ```rust for v in vals { /* ... */ } ``` is conceptually equivalent to: ```rust for v in vals.into_iter() { /* ... */ } ``` The key method here is `into_iter()`. This method takes ownership of the collection (hence `into_` in its name). Because `vals` has its ownership transferred to the iterator returned by `into_iter()`, `vals` itself is moved and is no longer valid for subsequent use after the loop finishes. The iterator produced by `into_iter()` yields the actual values (e.g., `u32` in our `Vec<u32>`). ## Iterating Without Consuming: Introducing `iter()` To iterate over a collection multiple times, or to use the collection after an iteration, you need an iterator that borrows the collection instead of consuming it. This is where the `iter()` method comes in. By explicitly calling `iter()`, you can loop over the collection while retaining ownership: ```rust // iter.rs fn main() { let vals: Vec<u32> = vec![1, 2, 3, 4, 5]; for v_ref in vals.iter() { // First loop, using iter() // v_ref is of type &u32 (an immutable reference) println!("First loop value: {}", v_ref); } // vals is still owned by main and available here for v_ref in vals.iter() { // Second loop, also using iter() // v_ref is of type &u32 println!("Second loop value: {}", v_ref); } } ``` This code compiles and runs successfully. The `vals.iter()` method takes an immutable reference to `vals` (`&self`) and produces an iterator that yields immutable references (`&T`, e.g., `&u32`) to the items in the vector. Since `vals` is only borrowed, it remains owned by the `main` function and can be borrowed again for the second loop. ## What is an Iterator? The `Iterator` Trait At its core, an iterator is any type that implements Rust's `Iterator` trait. This trait defines a standard way to produce a sequence of values. Many standard library types, such as `Vec<T>`, arrays (`[T; N]`), and `HashMap<K, V>`, implement `IntoIterator`, which provides methods like `into_iter()`, `iter()`, and `iter_mut()` that return types implementing the `Iterator` trait. Once you have an iterator, you can use it in a `for` loop, or call various adapter methods (like `map()`, `filter()`, `collect()`, etc.) to process its items. ## Mastering Iteration: `into_iter()`, `iter()`, and `iter_mut()` Collections in Rust typically offer three primary ways to create iterators, each with distinct behavior regarding ownership and the type of items yielded: 1. **`into_iter()`**: Consumes the collection to yield owned values. * **Conceptual Signature:** `fn into_iter(self) -> impl Iterator<Item = T>` * **Behavior:** Takes ownership of the collection (`self`). The collection is moved into the iterator. * **Item Type:** The iterator yields items of type `T` (the actual values). * **Consequence:** The original collection cannot be used after the iteration because its ownership has been moved. This is what happens with the default `for v in collection` loop. * **Example:** ```rust let vals: Vec<u32> = vec![1, 2, 3]; for v: u32 in vals.into_iter() { // v is of type u32 println!("Value: {}", v); } // `vals` is moved here and no longer accessible. // Attempting to use `vals` now would result in a compile error. ``` 2. **`iter()`**: Borrows the collection immutably to yield immutable references. * **Conceptual Signature:** `fn iter(&self) -> impl Iterator<Item = &T>` * **Behavior:** Takes an immutable reference to the collection (`&self`). The collection is borrowed. * **Item Type:** The iterator yields items of type `&T` (immutable references to the values). * **Consequence:** The original collection remains owned by its original scope and can be used after the iteration, or iterated over multiple times. You cannot modify the elements through these references. * **Example:** ```rust let vals: Vec<u32> = vec![1, 2, 3]; for v_ref: &u32 in vals.iter() { // v_ref is of type &u32 println!("Reference to value: {}", v_ref); } // `vals` is still available here. println!("Original vector after iter(): {:?}", vals); ``` 3. **`iter_mut()`**: Borrows the collection mutably to yield mutable references. * **Conceptual Signature:** `fn iter_mut(&mut self) -> impl Iterator<Item = &mut T>` * **Behavior:** Takes a mutable reference to the collection (`&mut self`). The collection is borrowed mutably. The collection itself must be declared as mutable (`let mut collection = ...`). * **Item Type:** The iterator yields items of type `&mut T` (mutable references to the values). * **Consequence:** The original collection remains owned, but it is mutably borrowed during the iteration. This allows you to modify the elements of the collection in place. * **Example:** ```rust let mut vals: Vec<u32> = vec![1, 2, 3]; // vals needs to be mutable for v_mut_ref: &mut u32 in vals.iter_mut() { // v_mut_ref is of type &mut u32 *v_mut_ref *= 2; // Modify the value by dereferencing } // `vals` is still available here, and its elements are modified. println!("Modified vector after iter_mut(): {:?}", vals); // Output: [2, 4, 6] ``` ## Quick Comparison: `into_iter()` vs. `iter()` vs. `iter_mut()` Here's a concise summary of the differences: * `into_iter()`: Iterates over `T`. Takes ownership of the collection, yielding its values. The collection is consumed. * `iter()`: Iterates over `&T`. Borrows the collection immutably, yielding immutable references to its values. The collection remains unchanged and usable. * `iter_mut()`: Iterates over `&mut T`. Borrows the collection mutably, yielding mutable references to its values. Allows modification of the collection's elements. The collection remains usable. ## Key Takeaways for Effective Iteration in Rust Understanding how Rust handles iteration is fundamental for writing correct and efficient code, especially given its ownership and borrowing rules: * The default `for v in collection` loop syntax implicitly uses `into_iter()`, which consumes the collection by moving its ownership. This prevents further use of the collection in its original scope. * To iterate over a collection multiple times or to retain ownership after iteration, explicitly use `iter()` for immutable access (yielding `&T`) or `iter_mut()` for mutable access (yielding `&mut T`). * When using `iter_mut()`, ensure the collection itself is declared as mutable (`let mut`). * Choosing the correct iterator method (`into_iter()`, `iter()`, or `iter_mut()`) depends on whether you need to consume the collection, merely read its elements, or modify them in place. This choice directly impacts how you interact with Rust's ownership system.
When working with collections in Rust, a common task is to iterate over their elements. The idiomatic for
loop syntax, for v in collection
, is straightforward but can lead to unexpected behavior if you need to access the collection after the loop.
Consider this scenario:
If you uncomment the second for
loop and try to compile this code, the Rust compiler will prevent it, issuing an error:
The error message error[E0382]: use of moved value: 'vals'
clearly indicates that vals
is no longer available for the second loop. This is because the first loop consumed the vector. The reason for this consumption lies in Rust's ownership system and how for
loops interact with iterators.
into_iter()
The for v in collection
syntax in Rust is syntactic sugar. Behind the scenes, it calls a method on the collection to get an iterator. Specifically, for a collection like Vec<T>
, this loop:
is conceptually equivalent to:
The key method here is into_iter()
. This method takes ownership of the collection (hence into_
in its name). Because vals
has its ownership transferred to the iterator returned by into_iter()
, vals
itself is moved and is no longer valid for subsequent use after the loop finishes. The iterator produced by into_iter()
yields the actual values (e.g., u32
in our Vec<u32>
).
iter()
To iterate over a collection multiple times, or to use the collection after an iteration, you need an iterator that borrows the collection instead of consuming it. This is where the iter()
method comes in.
By explicitly calling iter()
, you can loop over the collection while retaining ownership:
This code compiles and runs successfully. The vals.iter()
method takes an immutable reference to vals
(&self
) and produces an iterator that yields immutable references (&T
, e.g., &u32
) to the items in the vector. Since vals
is only borrowed, it remains owned by the main
function and can be borrowed again for the second loop.
Iterator
TraitAt its core, an iterator is any type that implements Rust's Iterator
trait. This trait defines a standard way to produce a sequence of values. Many standard library types, such as Vec<T>
, arrays ([T; N]
), and HashMap<K, V>
, implement IntoIterator
, which provides methods like into_iter()
, iter()
, and iter_mut()
that return types implementing the Iterator
trait.
Once you have an iterator, you can use it in a for
loop, or call various adapter methods (like map()
, filter()
, collect()
, etc.) to process its items.
into_iter()
, iter()
, and iter_mut()
Collections in Rust typically offer three primary ways to create iterators, each with distinct behavior regarding ownership and the type of items yielded:
into_iter()
: Consumes the collection to yield owned values.
Conceptual Signature: fn into_iter(self) -> impl Iterator<Item = T>
Behavior: Takes ownership of the collection (self
). The collection is moved into the iterator.
Item Type: The iterator yields items of type T
(the actual values).
Consequence: The original collection cannot be used after the iteration because its ownership has been moved. This is what happens with the default for v in collection
loop.
Example:
iter()
: Borrows the collection immutably to yield immutable references.
Conceptual Signature: fn iter(&self) -> impl Iterator<Item = &T>
Behavior: Takes an immutable reference to the collection (&self
). The collection is borrowed.
Item Type: The iterator yields items of type &T
(immutable references to the values).
Consequence: The original collection remains owned by its original scope and can be used after the iteration, or iterated over multiple times. You cannot modify the elements through these references.
Example:
iter_mut()
: Borrows the collection mutably to yield mutable references.
Conceptual Signature: fn iter_mut(&mut self) -> impl Iterator<Item = &mut T>
Behavior: Takes a mutable reference to the collection (&mut self
). The collection is borrowed mutably. The collection itself must be declared as mutable (let mut collection = ...
).
Item Type: The iterator yields items of type &mut T
(mutable references to the values).
Consequence: The original collection remains owned, but it is mutably borrowed during the iteration. This allows you to modify the elements of the collection in place.
Example:
into_iter()
vs. iter()
vs. iter_mut()
Here's a concise summary of the differences:
into_iter()
: Iterates over T
. Takes ownership of the collection, yielding its values. The collection is consumed.
iter()
: Iterates over &T
. Borrows the collection immutably, yielding immutable references to its values. The collection remains unchanged and usable.
iter_mut()
: Iterates over &mut T
. Borrows the collection mutably, yielding mutable references to its values. Allows modification of the collection's elements. The collection remains usable.
Understanding how Rust handles iteration is fundamental for writing correct and efficient code, especially given its ownership and borrowing rules:
The default for v in collection
loop syntax implicitly uses into_iter()
, which consumes the collection by moving its ownership. This prevents further use of the collection in its original scope.
To iterate over a collection multiple times or to retain ownership after iteration, explicitly use iter()
for immutable access (yielding &T
) or iter_mut()
for mutable access (yielding &mut T
).
When using iter_mut()
, ensure the collection itself is declared as mutable (let mut
).
Choosing the correct iterator method (into_iter()
, iter()
, or iter_mut()
) depends on whether you need to consume the collection, merely read its elements, or modify them in place. This choice directly impacts how you interact with Rust's ownership system.
A clarifying exploration of Rust's Core Iteration Methods: `into_iter`, `iter`, & `iter_mut` - Demystify why Rust's `for` loop consumes collections by default and how `iter()` enables repeated, non-consuming loops. Learn to wield `into_iter()` for owned values, `iter()` for shared references, and `iter_mut()` for in-place mutable access to your data.
Previous lesson
Previous
Next lesson
Next
Give us feedback
Course Overview
About the course
Introduction to the Rust programming language
Rust variables and functions
Scalar types, arrays, strings, enum, structs, vectors, and hash maps in Rust
Rust control flows: If / else, if let and let else, loop, match
Rust ownership, including borrow and references
Rust error handling
Rust Modules
Rust Traits
Last updated on July 11, 2025
Rust Developer
Rust Programming BasicsDuration: 6min
Duration: 18min
Duration: 47min
Duration: 15min
Duration: 19min
Duration: 8min
Duration: 12min
Duration: 46min
Duration: 14min
Course Overview
About the course
Introduction to the Rust programming language
Rust variables and functions
Scalar types, arrays, strings, enum, structs, vectors, and hash maps in Rust
Rust control flows: If / else, if let and let else, loop, match
Rust ownership, including borrow and references
Rust error handling
Rust Modules
Rust Traits
Last updated on July 11, 2025