Sling Academy
Home/Rust/Iterating over Rust Vectors with for loops and iterator adaptors

Iterating over Rust Vectors with for loops and iterator adaptors

Last updated: January 04, 2025

When working with the Rust programming language, iterating over collections, such as vectors, is a common task. Rust offers multiple ways to iterate over vectors, with each approach providing its own set of features and advantages. In this article, we’ll explore iterating over Rust vectors using for loops and iterator adaptors. This should give you a robust understanding about how to efficiently traverse and manipulate collections in Rust.

Using for Loops

One of the most straightforward methods to iterate over a vector in Rust is by using the for loop. This approach is intuitive for those coming from other languages with similar features.

fn main() {
    let numbers = vec![1, 2, 3, 4, 5];
    for number in &numbers {
        println!("{}", number);
    }
}

In the example above, the for loop iterates over references to the elements in the vector. Using the & symbol, we borrow each element, ensuring ownership remains with the original vector.

Consuming Ownership: Moving Elements

If you need to move elements from the vector and you no longer need to use the vector afterward, you can iterate over the vector by value.

fn main() {
    let numbers = vec![1, 2, 3, 4, 5];
    for number in numbers {
        println!("{}", number);
    }
    // numbers is not usable after this point
}

In this example, each element is moved out of the vector, which means the vector cannot be used later in the function, adhering to Rust's ownership principles.

Mutable Iteration

Sometimes, you may want to modify the elements as you iterate over them. In such cases, you can iterate over mutable references.

fn main() {
    let mut numbers = vec![1, 2, 3, 4, 5];
    for number in &mut numbers {
        *number *= 2;
    }
    println!("{:?}", numbers);
}

Using &mut, each element is accessed mutably, allowing changes in-place, such as doubling the value of each number in the vector.

Using Iterator Adaptors

Rust's standard library provides iterator adaptors, which are methods called on iterators that provide more advanced manipulation options. By converting a vector into an iterator using the iter() method, you can apply these adaptors.

Chaining and Collecting

One powerful feature is chaining adaptors, then collecting the results back into a data structure. Here’s an example where we filter and map elements:

fn main() {
    let numbers = vec![1, 2, 3, 4, 5];
    let doubled_even_numbers: Vec = numbers.into_iter()
        .filter(|&x| x % 2 == 0)
        .map(|x| x * 2)
        .collect();
    println!("{:?}", doubled_even_numbers);
}

This code will first filter for even numbers, double them, and then collect the results into a new vector.

The enumerate() Adaptor

The enumerate() adaptor is particularly handy when you need the index of each element while iterating:

fn main() {
    let numbers = vec![10, 20, 30, 40];
    for (i, number) in numbers.iter().enumerate() {
        println!("Index: {}, Value: {}", i, number);
    }
}

The enumerate() method transforms each element into a tuple, containing the index and a reference to the value, allowing more controlled iterations when needed.

Conclusion

Rust offers a versatile array of iteration options for vectors, from basic for loops leveraging memory safety to flexible iterator adaptors enabling fluent, functional transformations. Whether you aim for simplicity or advanced manipulations, understanding these iteration techniques is essential for effective Rust programming. Armed with these tools, you can tackle more complex data processing tasks efficiently and cleanly in Rust.

Next Article: Rust - Unwrapping Option results when accessing vector elements safely

Previous Article: Rust - Choosing HashMap vs BTreeMap: Trade-offs in performance and ordering

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