Question

Rust `iter()` vs `into_iter()`: Understanding Borrowed and Owned Iteration

rustiterator

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 &T
  • vec.into_iter() gives you T

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 reference
  • iter_mut() → modify items in place
  • into_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 values
  • array.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 string
  • into_iter() gives you String — 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.

Take Quiz

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 &i32 into i32
  • v is 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() yields i32
  • 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: &1 becomes x = 1 → 1 == 2 is false
  • second item: &2 becomes x = 2 → 2 == 2 is true

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:

  • 1
  • 2
  • 3

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 String values 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.

Take Quiz

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() consumes v

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:

  • x is &i32, not i32

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

MethodYieldsOwnership effectCan use original collection after?Common use
iter()&Tborrowsyesread-only traversal
iter_mut()&mut Tmutable borrowyes, after borrow endsmodify in place
into_iter()Tconsumesnomove values out

iter() vs into_iter()

Questioniter()into_iter()
Do I keep the collection?yesno
Do I get references?yesusually yes for borrowed collections, owned values for owned collections
Good for String without cloning?not by itselfyes
Best for read-only checks?yessometimes, 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

Collectioniter()into_iter()
Vec<T>&TT
[T; N] in modern Rust&TT
&[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.

Take Quiz

Cheat Sheet

Quick rules

  • iter() → borrow items as &T
  • iter_mut() → borrow items as &mut T
  • into_iter() → consume the collection and yield T

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 = inspect
  • iter_mut = edit
  • into_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.

Take Quiz

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 using iter().
  • Show that the original vector is still usable after the search.
  • Create a second Vec<String> that is transformed using into_iter().
  • Convert the second vector's items to uppercase and collect them into a new vector.
  • Print the final transformed vector.
Take Quiz

Keep learning

Related questions

View more