iter vs into_iter: Battle of the Iteration Titans in Rust
\n\nIf 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.
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.
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\nWhat is an Iterator in Rust?
\n\nAn 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.
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
Let's look at the code. Suppose we have a vector of names:
\n\nlet names = vec!["Anna".to_string(), "Boris".to_string(), "Victor".to_string()];\n\nExample 1: iter() — Just Looking
\n\nfn 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\nNotice: 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.
Example 2: into_iter() — Burn the Bridges
\n\nfn 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\nThere 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.
Example 3: iter_mut() — Change Without Destroying
\n\nfn 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\nHere we change the elements in place, but we don't take ownership. Perfect for transformations.
\n\nUnder the Hood: What Does Each Method Return?
\n\nTo truly understand iter vs into_iter, let's look into the standard library. For the vector Vec<T>:
- \n
iter()returnsstd::slice::Iter<T>— implementsIterator<Item = &T>