Question
I am working through the Rust By Example tutorial and came across this code:
// Vec example
let vec1 = vec![1, 2, 3];
let vec2 = vec![4, 5, 6];
// `iter()` for vecs yields `&i32`. Destructure to `i32`.
println!("2 in vec1: {}", vec1.iter().any(|&x| x == 2));
// `into_iter()` for vecs yields `i32`. No destructuring required.
println!("2 in vec2: {}", vec2.into_iter().any(|x| x == 2));
// Array example
let array1 = [1, 2, 3];
let array2 = [4, 5, 6];
// `iter()` for arrays yields `&i32`.
println!("2 in array1: {}", array1.iter().any(|&x| x == 2));
// `into_iter()` for arrays unusually yields `&i32`.
println!("2 in array2: {}", array2.into_iter().any(|&x| x == 2));
I am confused about the difference:
- For a
Vec,.iter()yields references and.into_iter()yields values. - For an array, these two seem to behave the same in this example.
What is the purpose of these two methods, and when should each one be used in Rust?
Short Answer
By the end of this page, you will understand how iter() and into_iter() differ in Rust, especially in terms of borrowing vs moving ownership. You will also see why Vec<T> and arrays can behave differently, how this changed across Rust editions, and how to choose the right iterator in real code.
Concept
In Rust, iter() and into_iter() are both ways to loop over a collection, but they represent different ownership behavior.
iter()borrows the collection and yields references to its items.into_iter()consumes the collection and yields owned items.
This distinction matters because Rust tracks ownership very strictly.
Core idea
For a collection like Vec<T>:
vec.iter()gives you&Tvec.into_iter()gives youT
That means:
- with
iter(), you can still use the original vector afterward because it was only borrowed - with
into_iter(), the vector is moved into the iterator, so you cannot use it afterward
There is also a third related method:
iter_mut()gives you&mut T
So the common pattern is:
iter()→ read items by referenceiter_mut()→ modify items in placeinto_iter()→ take ownership of items
Why this matters
This affects:
- whether you can use the original collection later
- whether cloning is needed
- whether transformations can move values out
- API design in functions and iterators
Why arrays seem different
The confusing part in older Rust examples is that arrays historically behaved differently from Vec<T> in method-call syntax.
In modern Rust:
[1, 2, 3].into_iter()produces owned valuesarray.iter()produces references
However, older tutorials and older edition behavior may show array .into_iter() acting like borrowing and yielding references in some contexts. That is why examples from older material can look inconsistent.
The important rule to remember
Do not memorize behavior by container alone. Instead, think in terms of ownership:
iter()means "borrow each item"into_iter()means "move each item out of the collection"
That mental model works across most Rust code.
Mental Model
Think of a collection as a box of items.
iter()is like opening the box and pointing at each item one by one. You are only looking, not taking.iter_mut()is like opening the box and adjusting each item in place.into_iter()is like handing over the whole box and letting someone take each item out. After that, you no longer own the box.
For a Vec<String>:
iter()gives you&String— you can read each stringinto_iter()gives youString— each string is moved out, so you now own it
This is why into_iter() is useful when you want to transform, return, or store values somewhere else without cloning them.
Syntax and Examples
Basic syntax
let v = vec![10, 20, 30];
for x in v.iter() {
println!("{}", x); // x is &i32
}
let v = vec![10, 20, 30];
for x in v.into_iter() {
println!("{}", x); // x is i32
}
Example: iter() borrows
let v = vec![1, 2, 3];
let has_two = v.iter().any(|&x| x == 2);
println!("has_two = {}", has_two);
println!("v is still usable: {:?}", v);
Here:
iter()yields&i32- the closure receives references, so
|&x|destructures&i32intoi32 vis still available afterward
Example: into_iter() consumes
let v = vec![1, 2, 3];
let has_two = v.into_iter().any(|x| x == 2);
println!("has_two = {}", has_two);
// println!("{:?}", v); // error: v was moved
Here:
into_iter()yieldsi32- the closure receives owned values
- the vector cannot be used afterward because it was consumed
Example with String
This example makes the difference more obvious:
let names = vec![String::from("Ana"), String::from("Ben")];
for name in names.iter() {
println!("Hello, {}", name); // &String
}
println!("Still have names: {:?}", names);
let names = vec![String::from("Ana"), String::from("Ben")];
for name in names.into_iter() {
println!("Owned name: {}", name); // String
}
// names can no longer be used here
Arrays in modern Rust
let arr = [1, 2, 3];
for x in arr.iter() {
println!("{}", x); // &i32
}
let arr = [1, 2, 3];
for x in arr.into_iter() {
println!("{}", x); // i32 in modern Rust
}
If you see older examples where array .into_iter() yields references, that usually reflects older edition behavior or older tutorial material.
Step by Step Execution
Consider this code:
let v = vec![1, 2, 3];
let found = v.iter().any(|&x| x == 2);
println!("found = {}", found);
println!("v = {:?}", v);
Step by step
1. Create the vector
let v = vec![1, 2, 3];
v owns the vector and its contents.
2. Call iter()
v.iter()
This creates an iterator that borrows v and yields items of type &i32.
The iterator will produce:
&1&2&3
3. Call any(...)
.any(|&x| x == 2)
The closure receives each item from the iterator.
Since each item is &i32, the pattern |&x| destructures the reference:
- first item:
&1becomesx = 1→1 == 2isfalse - second item:
&2becomesx = 2→2 == 2istrue
At this point, any() stops early and returns true.
4. Print the result
println!("found = {}", found);
This prints:
found = true
5. Use v again
println!("v = {:?}", v);
This works because iter() only borrowed the vector.
Now compare with:
let v = vec![1, 2, 3];
let found = v.into_iter().any(|x| x == 2);
println!("found = {}", found);
What changes?
1. into_iter() takes ownership
v.into_iter()
This moves v into the iterator.
2. Items are owned values
The iterator yields:
123
So the closure can simply be:
|x| x == 2
3. v is no longer available
After this, v has been consumed. Trying to use it again causes a compile error.
Real World Use Cases
When iter() is used
iter() is common when you want to read data without taking ownership.
Examples
- checking whether a value exists in a list
- calculating a sum from borrowed data
- filtering items while keeping the original collection
- rendering data in a web server response
- inspecting configuration values
let users = vec!["alice", "bob", "carol"];
let has_bob = users.iter().any(|&name| name == "bob");
When into_iter() is used
into_iter() is useful when you want to consume a collection and move its values elsewhere.
Examples
- converting one collection into another
- moving
Stringvalues without cloning - building transformed output from owned input
- passing ownership into worker tasks or channels
let words = vec![String::from("a"), String::from("bb")];
let lengths: Vec<usize> = words.into_iter().map(|s| s.len()).collect();
This is efficient because each String is moved, not cloned.
When arrays are used
Arrays are common for fixed-size data:
- RGB values like
[255, 0, 128] - coordinates like
[x, y, z] - small fixed lookup data
In modern Rust, into_iter() on arrays is useful when you want to consume those fixed values directly.
Real Codebase Usage
In real Rust projects, developers choose between these iterator styles based on ownership needs.
Pattern: read-only processing with iter()
fn contains_admin(users: &[String]) -> bool {
users.iter().any(|name| name == "admin")
}
This is common because:
- it avoids taking ownership
- it works with slices and borrowed data
- it keeps APIs flexible
Pattern: transformation with into_iter()
fn uppercase_all(words: Vec<String>) -> Vec<String> {
words
.into_iter()
.map(|word| word.to_uppercase())
.collect()
}
This is common when the function receives ownership and returns a new owned collection.
Pattern: mutate in place with iter_mut()
fn increment_all(values: &mut [i32]) {
for value in values.iter_mut() {
*value += 1;
}
}
Pattern: early-return validation
fn all_positive(values: &[i32]) -> bool {
values.iter().all(|&x| x > 0)
}
Pattern: moving data into pipelines
let files: Vec<String> = get_file_names();
let json_files: Vec<String> = files
.into_iter()
.filter(|name| name.ends_with(".json"))
.collect();
This style is common in data-processing code because ownership moves cleanly through the iterator pipeline.
Common Mistakes
1. Using into_iter() when you still need the collection
Broken code:
let v = vec![1, 2, 3];
let sum: i32 = v.into_iter().sum();
println!("{:?}", v); // error
Why it fails:
into_iter()consumesv
Fix:
let v = vec![1, 2, 3];
let sum: i32 = v.iter().sum();
println!("{:?}", v);
2. Forgetting that iter() yields references
Broken code:
let v = vec![1, 2, 3];
let has_two = v.iter().any(|x| x == 2);
Why it fails:
xis&i32, noti32
Fixes:
let has_two = v.iter().any(|&x| x == 2);
or:
let has_two = v.iter().any(|x| *x == 2);
3. Confusing arrays and vectors from older examples
If an old tutorial says array .into_iter() yields references, check:
- the Rust edition
- the compiler version
- whether the code is relying on older behavior
In modern Rust, array into_iter() yields owned items.
4. Cloning unnecessarily
Less efficient:
let names = vec![String::from("a"), String::from("b")];
let copied: Vec<String> = names.iter().cloned().collect();
If you no longer need names, prefer:
let names = vec![String::from("a"), String::from("b")];
let moved: Vec<String> = names.into_iter().collect();
5. Forgetting iter_mut() for modification
Broken idea:
let mut v = vec![1, 2, 3];
for x in v.iter() {
// x += 1; // cannot modify through shared reference
}
Fix:
let mut v = vec![1, 2, 3];
for x in v.iter_mut() {
*x += 1;
}
Comparisons
Iterator methods compared
| Method | Yields | Ownership effect | Can use original collection after? | Common use |
|---|---|---|---|---|
iter() | &T | borrows | yes | read-only traversal |
iter_mut() | &mut T | mutable borrow | yes, after borrow ends | modify in place |
into_iter() | T | consumes | no | move values out |
iter() vs into_iter()
| Question | iter() | into_iter() |
|---|---|---|
| Do I keep the collection? | yes | no |
| Do I get references? | yes | usually yes for borrowed collections, owned values for owned collections |
Good for String without cloning? | not by itself | yes |
| Best for read-only checks? | yes | sometimes, but often unnecessary |
for loop behavior
A for loop automatically calls into_iter() on the thing you give it.
for x in vec![1, 2, 3] {
println!("{}", x);
}
This consumes the temporary vector.
If you want borrowing instead:
let v = vec![1, 2, 3];
for x in &v {
println!("{}", x);
}
This is similar to v.iter().
Arrays vs vectors
| Collection | iter() | into_iter() |
|---|---|---|
Vec<T> | &T | T |
[T; N] in modern Rust | &T | T |
&[T] | &T | &T |
That last row is important: calling into_iter() on a reference to a collection iterates by reference because the reference itself is what gets consumed.
Cheat Sheet
Quick rules
iter()→ borrow items as&Titer_mut()→ borrow items as&mut Tinto_iter()→ consume the collection and yieldT
Common patterns
v.iter()
v.iter_mut()
v.into_iter()
For Vec<T>
let v = vec![1, 2, 3];
v.iter(); // Iterator<Item = &i32>
v.into_iter(); // Iterator<Item = i32>
For arrays in modern Rust
let a = [1, 2, 3];
a.iter(); // Iterator<Item = &i32>
a.into_iter(); // Iterator<Item = i32>
If you have a reference
let v = vec![1, 2, 3];
(&v).into_iter(); // Iterator<Item = &i32>
Because the thing being consumed is &Vec<i32>, not Vec<i32>.
Closure patterns
v.iter().any(|&x| x == 2)
v.iter().any(|x| *x == 2)
v.into_iter().any(|x| x == 2)
When to choose which
- Need to keep the collection? Use
iter() - Need to modify items? Use
iter_mut() - Need to move items out? Use
into_iter()
Easy memory trick
iter= inspectiter_mut= editinto_iter= take ownership
FAQ
Why does iter() return references in Rust?
Because iter() borrows the collection instead of consuming it. Returning references lets you access items without taking ownership.
Why does into_iter() consume the collection?
Its purpose is to turn the collection itself into an iterator. That usually means ownership moves into the iterator, so the original collection is no longer available.
Is into_iter() always better than iter()?
No. Use into_iter() only when you want to consume the collection. If you still need the collection afterward, use iter().
Why do old Rust examples show arrays behaving differently?
Because array into_iter() behavior changed over time, especially across Rust editions and older tutorial material. Modern Rust treats array into_iter() as yielding owned items.
What does a for loop use internally?
A for loop calls IntoIterator::into_iter() on the value you provide.
When should I use iter_mut()?
Use it when you want to change items inside a collection without moving them out.
Can into_iter() avoid cloning?
Yes. If you own the collection and no longer need it, into_iter() lets you move values out directly, which is often more efficient than cloning.
Mini Project
Description
Build a small Rust program that processes a list of task names to demonstrate the difference between borrowing and consuming iteration. You will inspect tasks with iter(), then transform owned task strings with into_iter(). This mirrors real applications where data is first checked, then moved into a new processed form.
Goal
Create a program that checks whether a task exists without consuming the original list, then consumes another list to build uppercase versions without cloning.
Requirements
- Create one
Vec<String>that is searched usingiter(). - Show that the original vector is still usable after the search.
- Create a second
Vec<String>that is transformed usinginto_iter(). - Convert the second vector's items to uppercase and collect them into a new vector.
- Print the final transformed vector.
Keep learning
Related questions
Absolute Values in Rust: Using the abs() Method
Learn how to calculate absolute values in modern Rust with abs(), handle signed integer overflow, and choose safer alternatives.
Accepting Iterators in Rust Functions with impl Iterator
Learn how to write Rust functions that accept HashMap iterators using generic bounds or impl Iterator, avoiding trait object and lifetime errors.
Access Enum Variant Values in Rust with Pattern Matching
Learn how to extract and print values stored in Rust enum variants using match and if let pattern matching, with practical examples.