iter vs into_iter in Rust: What's Under the Hood?

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

iter vs into_iter: Battle of the Iteration Titans in Rust

\n\n

If you've ever written a loop over a vector in Rust, you've likely encountered the three triplet brothers: iter(), into_iter(), and iter_mut(). At first glance, they all do the same thing — allow you to iterate over elements. But the difference between them is deeper than it seems, and it lies at the very heart of Rust — the ownership system.

\n\n

Imagine you're a barista, and your collection is coffee beans. iter() lets you look at the beans, smell them, but not touch them. into_iter() is when you take the beans, grind them, and make espresso, after which the beans disappear. And iter_mut() — you can not only look but also grind the beans right in the bag, changing their shape.

\n\n

Today we'll break down iter vs into_iter the way it's explained on Stack Overflow, but with live examples and a deep dive under the hood. Let's go!

\n\n

What is an Iterator in Rust?

\n\n

An iterator is an object that knows how to sequentially retrieve elements from a collection. In Rust, iterators are lazy: they don't do anything until you call next(). But the main thing is that each iteration method treats data ownership differently.

\n\n

Here are the three main approaches:

\n
    \n
  • iter() — borrows elements (does not take ownership)
  • \n
  • into_iter() — consumes the collection (takes ownership)
  • \n
  • iter_mut() — borrows with the ability to modify
  • \n
\n\n

Let's look at the code. Suppose we have a vector of names:

\n\n
let names = vec!["Anna".to_string(), "Boris".to_string(), "Victor".to_string()];
\n\n

Example 1: iter() — Just Looking

\n\n
fn main() {\n    let names = vec!["Anna".to_string(), "Boris".to_string(), "Victor".to_string()];\n    \n    // iter() returns &String (a reference)\n    for name in names.iter() {\n        println!("Hello, {}!", name);\n    }\n    \n    // names is still accessible!\n    println!("Vector is alive: {:?}", names);\n}
\n\n

Notice: after the loop, we can still use names. Why? Because iter() returned a reference to each element. Ownership remained with the vector. This is safe and predictable.

\n\n

Example 2: into_iter() — Burn the Bridges

\n\n
fn main() {\n    let names = vec!["Anna".to_string(), "Boris".to_string(), "Victor".to_string()];\n    \n    // into_iter() returns String (ownership is moved)\n    for name in names.into_iter() {\n        println!("Goodbye, {}!", name);\n    }\n    \n    // Error! names no longer exists\n    // println!("Vector is alive: {:?}", names); // ❌ borrow of moved value\n}
\n\n

There it is, the key difference. into_iter() consumes the collection. Each element is moved into the loop, and the original vector becomes invalid. If you need to keep the collection for further use — into_iter() is not your choice.

\n\n

Example 3: iter_mut() — Change Without Destroying

\n\n
fn main() {\n    let mut names = vec!["Anna".to_string(), "Boris".to_string(), "Victor".to_string()];\n    \n    // iter_mut() returns &mut String (a mutable reference)\n    for name in names.iter_mut() {\n        name.push_str(" (guest)");\n    }\n    \n    println!("Modified vector: {:?}", names);\n    // Output: ["Anna (guest)", "Boris (guest)", "Victor (guest)"]\n}
\n\n

Here we change the elements in place, but we don't take ownership. Perfect for transformations.

\n\n

Under the Hood: What Does Each Method Return?

\n\n

To truly understand iter vs into_iter, let's look into the standard library. For the vector Vec<T>:

\n\n