Published on

Rust Iteration: Understanding `IntoIterator` vs `iter()`

Authors
  • Mehdi Akiki avatar
    Name
    Mehdi Akiki
    Twitter

When working with collections in Rust, you have multiple ways to create iterators. The most common approaches are using IntoIterator or calling the iter() method. Though they may seem similar at first glance, they serve different purposes, and understanding when to use each one can help you write more expressive and efficient Rust code.

In this article, we will explore the differences between IntoIterator and iter() with examples to help you understand the different iteration behaviors they provide. If you come from other programming languages, think of these two as different ways of looping through elements, with key distinctions in how they interact with ownership and mutability.

IntoIterator Overview

IntoIterator is a trait that is implemented for types that can be converted into an iterator. When you use a for loop in Rust, you are actually using IntoIterator under the hood. This trait allows for a collection to be converted into something that can iterate over its elements.

One way to understand IntoIterator is by comparing it to concepts in other languages. If you've worked with C++, it's somewhat similar to converting an object to an iterator to iterate over the elements by value. In Python, it would be like using a generator that yields items, meaning the original collection is no longer available.

Example with IntoIterator

let numbers = vec![1, 2, 3, 4];

for num in numbers.into_iter() {
    println!("{}", num);
}

In this example, into_iter() consumes the numbers vector, meaning the vector cannot be used afterward. It moves the ownership of the elements out of the vector, which is useful if you want to avoid making copies or need to mutate the vector elements.

Characteristics

  • Consumes the Collection: into_iter() moves ownership of the collection, rendering it unusable afterward.
  • Implemented for Collections: It works with various collections like Vec, HashMap, and more.
  • Ownership Transfer: It transfers ownership of each element, meaning the original collection is no longer accessible after iteration.

iter() Overview

The iter() method, on the other hand, creates an iterator that borrows the collection, allowing iteration without consuming it. This is equivalent to read-only access. In other languages, you might think of this like looping over elements without modifying the original collection. For example, in Python, it would be similar to using a for loop over a list without changing the original list.

Example with iter()

let numbers = vec![1, 2, 3, 4];

for num in numbers.iter() {
    println!("{}", num);
}

// The vector can still be used here
do_something_else(&numbers);

In this example, iter() borrows the vector, meaning you can still use numbers after the loop. This is helpful when you need to access the collection again after iterating over it.

Characteristics

  • Borrows the Collection: iter() borrows the collection, leaving the original intact and usable.
  • Non-Destructive: Since the original collection is borrowed, it can still be used after iteration.
  • Read-Only Access: It provides a view into the elements without taking ownership or allowing modification.

Differences Between IntoIterator and iter()

  1. Ownership:

    • into_iter(): Moves ownership of the collection, consuming it. This is akin to "moving" the original data, making it no longer accessible.
    • iter(): Borrows the collection, allowing it to be used again after iteration. This is like passing a reference that keeps the original data intact.
  2. Usage Context:

    • into_iter(): Ideal when you no longer need the original collection or want to mutate its contents while iterating. This is similar to taking full control over the elements.
    • iter(): Suitable when you need to iterate without altering the collection or when the collection is required afterward.
  3. Mutability:

    • iter(): Only allows read-only access.
    • iter_mut(): This variant of iter() gives mutable references, allowing modification of the elements during iteration.
    let mut numbers = vec![1, 2, 3, 4];
    
    for num in numbers.iter_mut() {
        *num *= 2;
    }
    
    println!("{:?}", numbers); // Output: [2, 4, 6, 8]
    

    iter_mut() is a useful way to modify elements in place without consuming the entire collection. This is similar to how you might modify elements in a loop with direct access to each element.

Examples with Collections

Iterating Over HashMap

  • into_iter() Example:

    use std::collections::HashMap;
    
    let mut map = HashMap::new();
    map.insert("a", 1);
    map.insert("b", 2);
    
    for (key, value) in map.into_iter() {
        println!("{}: {}", key, value);
    }
    // The map is now consumed and cannot be used again.
    
  • iter() Example:

    use std::collections::HashMap;
    
    let mut map = HashMap::new();
    map.insert("a", 1);
    map.insert("b", 2);
    
    for (key, value) in map.iter() {
        println!("{}: {}", key, value);
    }
    // The map is still usable here.
    

    In the above examples, into_iter() transfers ownership, while iter() borrows each element, maintaining the integrity of the original HashMap.

Summary

In Rust, both into_iter() and iter() provide powerful ways to iterate over collections, each with distinct use cases. into_iter() consumes the collection, transferring ownership of each element, making it perfect when you want to process and then discard or modify the original collection. iter(), on the other hand, borrows the collection, preserving it for further use, which is ideal for read-only iteration scenarios.

For programmers coming from other languages, think of into_iter() as a way to take complete control and "destroy" the original collection, while iter() simply gives you a view into the collection without altering it. Understanding the difference helps you choose the right method depending on whether you need ownership of the collection or wish to keep it intact.

FAQ

What is the main difference between iter() and into_iter()?

iter() borrows the collection, allowing you to use it afterward, while into_iter() consumes the collection, transferring ownership of its elements.

When should I use iter() instead of into_iter()?

Use iter() when you need to iterate without consuming the collection, especially if you need to use it later. into_iter() is useful when you want to transfer ownership and do not need the collection after iteration.

Can I modify elements with iter()?

No, iter() only provides read-only access. To modify elements, use iter_mut() which gives mutable references to the elements.

Does into_iter() work differently for arrays?

For arrays, into_iter() will create an iterator that yields each element by value, whereas iter() will yield each element by reference.

Is iter_mut() destructive like into_iter()?

No, iter_mut() borrows the collection mutably, allowing you to modify elements without consuming the collection itself. This allows you to keep the original collection while still being able to change its contents.

How does into_iter() behave differently depending on the type?

The behavior of into_iter() changes depending on the type it is called on. For example, calling into_iter() on a vector consumes it, yielding owned values. However, calling into_iter() on a reference to a vector yields references to its elements, not consuming the vector itself.

Why does iter() always yield references?

iter() is designed to provide read-only access to a collection, which means it yields references (&T). This allows you to iterate over the elements without taking ownership, preserving the original collection.

What are the three variations of IntoIterator for Vec?

Vec implements IntoIterator in three ways:

  • Vec<T>: Consumes the vector, yielding owned values (T).
  • &Vec<T>: Borrows the vector, yielding immutable references (&T).
  • &mut Vec<T>: Borrows the vector mutably, yielding mutable references (&mut T).

Is &vec equivalent to vec.iter()?

Yes, using &vec in a for loop is equivalent to calling vec.iter(). Both approaches provide an iterator that yields references to the elements (&T), but using &vec is often more concise in loops.

Can into_iter() yield mutable references?

Yes, if into_iter() is called on a mutable reference (&mut Vec<T>), it yields mutable references (&mut T). This allows you to modify elements while iterating, without consuming the original collection.

I build and scale reliable production systems. Open to full-time and freelance work with U.S.-based teams that value ownership and execution.

Got something in mind?

Book a Discovery Call