---
title: "19.6 For loops, demystified"
description: "How a for loop desugars into IntoIterator plus repeated next calls, finally paying off the promise made back in chapter 7."
url: "https://learnrust.net/chapter-19/for-loops-demystified/"
last_updated: "2026-06-13"
---

# 19.6 For loops, demystified

Back in lesson [7.5](https://learnrust.net/chapter-7/for-loops-and-ranges/) we introduced the `for` loop and promised that one day you'd see what it really is. You now have every piece: the `Iterator` trait ([19.3](https://learnrust.net/chapter-19/the-iterator-trait/)), `next`, and the `iter`/`iter_mut`/`into_iter` trio. This short lesson assembles them and pays the debt in full. A `for` loop is not a primitive of the language with special looping powers. It's a thin convenience that expands into an iterator and a `while let`.

## What `for` actually does

Take the simplest possible loop:

```rust
let v = vec![10, 20, 30];
for x in v {
    println!("{x}");
}
```

The compiler rewrites this, roughly, into the following before compiling it:

```rust
let v = vec![10, 20, 30];
let mut iter = v.into_iter();
while let Some(x) = iter.next() {
    println!("{x}");
}
```

That's the whole trick. `for x in thing` does three things: call `into_iter()` on `thing` to get an iterator, then repeatedly call `next()`, binding each `Some(x)` to the loop variable and running the body, and stop the instant `next()` returns `None`. The `while let` you met in lesson [11.6](https://learnrust.net/chapter-11/if-let-while-let-let-else/) is doing the looping; the `Option` from `next` is doing the "are we done?" decision. There's no magic looping construct underneath, just `next` in a loop.

Run both versions and they print identically:

```
10
20
30
```

## IntoIterator: the trait `for` calls

The method `for` calls is `into_iter`, and it comes from a trait named **`IntoIterator`**: "this type can be turned into an iterator." Anything that implements `IntoIterator` can sit on the right of `for ... in`. Vectors implement it, ranges implement it, arrays implement it, hash maps implement it. That's *why* all of those work in a `for` loop, they each say how to become an iterator, and `for` calls that method.

This also finally explains the `&` you've been writing in loop headers for nineteen chapters. References to collections implement `IntoIterator` too, and they do it by delegating to the borrowing iterators:

```rust
for x in &v        // calls (&v).into_iter()  ->  v.iter(),      x is &T
for x in &mut v    // calls (&mut v).into_iter() -> v.iter_mut(), x is &mut T
for x in v         // calls v.into_iter(),                        x is T (consumes v)
```

So `for x in &v` was *never* a special borrowing form of `for`. It was an ordinary `for` over `&v`, and `&v`'s `into_iter` is `iter`. The three loop headers you've been choosing between are just three different things implementing `IntoIterator`, the value, the shared reference, and the mutable reference, each turning into the matching iterator. The choice you made at the top of every loop, `v`, `&v`, or `&mut v`, was choosing ownership versus borrow all along, dressed up as loop syntax.

> **Key insight**
>
> `for` is sugar for `IntoIterator::into_iter` followed by `while let Some(x) = iter.next()`. Everything you learned about iterators, laziness, the `Item` type, `iter` versus `into_iter`, applies to every `for` loop you've ever written, because every `for` loop *is* an iterator being driven. The loop was the iterator wearing a familiar coat the whole time.

## Why this matters in practice

This isn't only trivia. Three practical consequences fall straight out of it.

First, anything that's an iterator works in a `for` loop with no `.collect()` in between, because `for` consumes iterators directly. `for (i, x) in v.iter().enumerate()` works because the adapter chain is already an iterator (every `Iterator` is trivially `IntoIterator`, it returns itself). You don't gather the adapter output into a vector first; `for` drives it as-is.

Second, the choice between a `for` loop and an adapter pipeline is a *style* choice, not a capability one, because they compile to the same machinery. A `for` loop with a mutable accumulator and the equivalent `fold` produce the same code; pick whichever reads more clearly for the task. The next lesson ([19.7](https://learnrust.net/chapter-19/loops-vs-iterators/)) gives that decision the attention it deserves.

Third, you can make your *own* types loopable. Implement `IntoIterator` for a type you wrote, and `for item in my_value` just works, calling your `into_iter`. That's how a custom collection becomes a first-class citizen, indistinguishable in use from `Vec`. It's beyond what we'll build here, but the door is now visibly open: the loop syntax was never reserved for built-in types.

## Quiz time

**Question #1**

Rewrite `for x in nums { sum += x; }` as the `while let` loop the compiler turns it into.

<details class="solution">
<summary>Show solution</summary>

```rust
let mut iter = nums.into_iter();
while let Some(x) = iter.next() {
    sum += x;
}
```

`for` calls `into_iter()` on `nums` to get an iterator, then loops with `while let Some(x) = iter.next()`, running the body for each value and stopping when `next()` returns `None`.

</details>

**Question #2**

After this lesson, what is `for x in &v` actually doing, in terms of iterators?

<details class="solution">
<summary>Show solution</summary>

It's an ordinary `for` loop over the value `&v`. `&v` implements `IntoIterator`, and its `into_iter()` is `v.iter()`, which yields `&T`. So `for x in &v` is just `for` calling `(&v).into_iter()`, i.e. `v.iter()`. It was never a special "borrowing for"; the `&` chose the shared-reference iterator, and `for x in &mut v` / `for x in v` similarly pick `iter_mut` / `into_iter`.

</details>

**Question #3**

Why can you write `for (i, c) in text.chars().enumerate()` without calling `.collect()` first?

<details class="solution">
<summary>Show solution</summary>

Because `text.chars().enumerate()` is already an iterator, and every iterator implements `IntoIterator` (its `into_iter` returns itself). `for` calls `into_iter` on whatever follows `in`, so it drives the adapter chain directly. Collecting into a `Vec` first would just build a throwaway collection for `for` to take apart again.

</details>

You've now seen that loops and iterator pipelines are two spellings of the same thing. The final lesson before the quiz ([19.7](https://learnrust.net/chapter-19/loops-vs-iterators/)) weighs them against each other: performance (with a real benchmark), readability, and when each is the right call.

## Sitemap

See the full [sitemap](https://learnrust.net/sitemap.md) for all pages.
