Sling Academy
Home/Rust/Rust - Understanding how iterators work internally for Vec<T>, LinkedList<T>, and HashMap<K, V>

Rust - Understanding how iterators work internally for Vec, LinkedList, and HashMap

Last updated: January 04, 2025

When working with collections in Rust, iterators are a fundamental concept that allows you to traverse data structures efficiently. In this article, we will delve into how iterators internally work for three common Rust structures: Vec<T>, LinkedList<T>, and HashMap<K, V>. Understanding these underlying mechanisms not only enables us to write efficient Rust programs but also deepens our grasp over these basic Rust data structures.

Iterators in Rust

In Rust, an iterator is any object that implements the Iterator trait, which provides a way of iterating over a sequence of values. The core method of this trait is next(), which returns an Option: it returns Some(Item) when there are more items, and None once iteration is complete.

Vec<T> Iteration

The Vec<T> structure represents a dynamically-sized array in Rust. Internally, iterating over a vector involves accessing memory locations sequentially where the vector's data is stored. Here's a basic code example demonstrating how we can iterate over a vector:

fn main() {
    let vec = vec![1, 2, 3, 4, 5];
    for value in vec.iter() {
        println!("{}", value);
    }
}

Internally, a vector is composed of a contiguous block of memory, and the iterator simply points to the beginning of this block. The next() function increases the pointer to the next item in the block, thereby moving through the vector.

LinkedList<T> Iteration

The LinkedList<T> in Rust implements a doubly-linked list. Each node in the list holds a value and links to the next and previous nodes. Here’s a base example:

use std::collections::LinkedList;

fn main() {
    let mut list = LinkedList::new();
    list.push_back(1);
    list.push_back(2);
    list.push_back(3);
    
    for value in list.iter() {
        println!("{}", value);
    }
}

The LinkedList iterator traverses by hopping from the current node to the next node following these links until no further nodes are found. It provides direct access to each node through these links.

HashMap<K, V> Iteration

A HashMap<K, V> contains key-value pairs. In Rust, it is implemented using an array of buckets, each potentially holding one or more entries that hash to the same bucket. Here is how you can iterate over a hash map:

use std::collections::HashMap;

fn main() {
    let mut map = HashMap::new();
    map.insert("one", 1);
    map.insert("two", 2);
    map.insert("three", 3);
    
    for (key, value) in map.iter() {
        println!("{}: {}", key, value);
    }
}

Iterating over a hash map involves advancing through all the possible buckets and then going through each entry of a non-empty bucket. In a case of hash collision, it handles iterating through the chain of entries found within that particular bucket.

Conclusion

Understanding the internal workings of iterators on different data structures like Vec, LinkedList, and HashMap opens up insights on performance characteristics, particularly with collection traversal and operation costs. Knowing where each data structure shines or falls short allows us to optimize our code adhering to Rust's powerful safety and concurrency features effectively.

Next Article: Rust: Creating a HashMap with capacity to reduce reallocation

Previous Article: Rust - Exploring double-ended iteration over LinkedList with iter and iter_mut

Series: Collections in Rust

Rust

You May Also Like

  • E0557 in Rust: Feature Has Been Removed or Is Unavailable in the Stable Channel
  • Network Protocol Handling Concurrency in Rust with async/await
  • Using the anyhow and thiserror Crates for Better Rust Error Tests
  • Rust - Investigating partial moves when pattern matching on vector or HashMap elements
  • Rust - Handling nested or hierarchical HashMaps for complex data relationships
  • Rust - Combining multiple HashMaps by merging keys and values
  • Composing Functionality in Rust Through Multiple Trait Bounds
  • E0437 in Rust: Unexpected `#` in macro invocation or attribute
  • Integrating I/O and Networking in Rust’s Async Concurrency
  • E0178 in Rust: Conflicting implementations of the same trait for a type
  • Utilizing a Reactor Pattern in Rust for Event-Driven Architectures
  • Parallelizing CPU-Intensive Work with Rust’s rayon Crate
  • Managing WebSocket Connections in Rust for Real-Time Apps
  • Downloading Files in Rust via HTTP for CLI Tools
  • Mocking Network Calls in Rust Tests with the surf or reqwest Crates
  • Rust - Designing advanced concurrency abstractions using generic channels or locks
  • Managing code expansion in debug builds with heavy usage of generics in Rust
  • Implementing parse-from-string logic for generic numeric types in Rust
  • Rust.- Refining trait bounds at implementation time for more specialized behavior