Sling Academy
Home/Rust/Rust - Exploring double-ended iteration over LinkedList<T> with iter and iter_mut

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

Last updated: January 04, 2025

Linked lists are extensively used data structures due to their dynamic nature, allowing efficient insertions and deletions. In Rust, the LinkedList<T> offers a rich choice of iteration patterns through iter and iter_mut. These methods respectively provide immutable and mutable views of the elements. Leveraging these iteration methods allows Rust developers to traverse linked lists bidirectionally, giving flexibility similar to what doubly linked lists offer.

Understanding LinkedList in Rust

The LinkedList is a list where each element points to its predecessor and successor. This double-ended nature accommodates efficient operations at both ends, which we can leverage for tasks requiring keen control over traversal direction and order.

Basic Operations on LinkedList<T>

Let’s begin with basic operations:

use std::collections::LinkedList;

fn main() {
    let mut list: LinkedList<i32> = LinkedList::new();
    list.push_back(1);
    list.push_back(2);
    list.push_front(3);
    
    println!("{:?}", list);
}

In the snippet above, we construct a LinkedList of integers, adding elements with push_back and push_front. Printing shows its construction, with expected outputs like [3, 1, 2].

Using Iterators

The iter and iter_mut functions allow developers to access the elements. They yield an iterator over the primary list elements, either as immutable or mutable references.

Iterating with iter

The iter method gives a bidirectional iterator with read-only access:

fn print_elements(list: &LinkedList<i32>) {
    for value in list.iter() {
        println!("{}", value);
    }
}

fn main() {
    let list = create_sample_list();
    print_elements(&list);
}

fn create_sample_list() -> LinkedList<i32> {
    let mut list = LinkedList::new();
    list.push_back(4);
    list.push_back(5);
    list.push_back(6);
    list
}

In this example, the function print_elements iterates over other elements, displaying each one.

Iterating with iter_mut

The iter_mut method empowers developers by allowing modifications to the list's elements:

fn double_elements(list: &mut LinkedList<i32>) {
    for value in list.iter_mut() {
        *value *= 2;
    }
}

fn main() {
    let mut list = create_sample_list();
    double_elements(&mut list);
    for value in list.iter() {
        println!("{}", value);
    }
}

In this case, double_elements modifies each list element by doubling its value. Printing the elements afterward displays each part multiplied accordingly.

Using Double-Ended Iterators

Rust's iterator semantics include double-ended iteration, enabling traversal from either direction within the list:

fn print_reverse(list: &LinkedList<i32>) {
    for value in list.iter().rev() {
        println!("{}", value);
    }
}

fn main() {
    let list = create_sample_list();
    print_reverse(&list);
}

The rev() method reverses the usual iteration order effectively. As demonstrated, print_reverse will process elements starting from the back of the list.

Comprehensive Handling with Double-Ended Iterators

Understanding these technical intricacies is crucial for programming tasks that require detailed, precise list handling. Double-ended iterators embody this flexibility, defining interfaces for accessing forward and backward iterators seamlessly.

In summary, Rust's iterator protocols enhance LinkedList<T>'s utility by augmenting how developers assign, access, and process data. This technique fundamentally alters approachability in mutable contexts, thereby enriching a programmer's operational toolkit.

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

Previous Article: Rust - Implementing custom algorithms on LinkedList: merges, splits, and more

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