Module: Collections

Vectors

Rust Programming: Vectors

Vectors are a resizable array type in Rust. They are one of the most commonly used collection types. Here's a breakdown of vectors in Rust, covering creation, manipulation, and common operations.

What is a Vector?

  • Resizable: Unlike fixed-size arrays, vectors can grow or shrink in size as needed.
  • Homogeneous: Vectors can only store elements of the same type. This is enforced at compile time, providing type safety.
  • Heap Allocation: Vectors store their elements on the heap, allowing for dynamic resizing.
  • Ownership: Vectors own the data they contain. When a vector goes out of scope, the memory it allocated is automatically freed.

Creating Vectors

There are several ways to create vectors:

1. Vec::new(): Creates an empty vector.

let mut my_vector: Vec<i32> = Vec::new(); // Explicit type annotation
let mut another_vector = Vec::new(); // Type inference (empty vector)

2. vec![] Macro: A convenient macro for creating vectors with initial values.

let numbers = vec![1, 2, 3, 4, 5]; // Vector of i32
let strings = vec!["hello", "world"]; // Vector of &str
let empty_vec: Vec<f64> = vec![]; // Empty vector of f64

3. Vec::with_capacity(): Creates a vector with a pre-allocated capacity. This can improve performance if you know the approximate size of the vector beforehand, as it reduces the number of reallocations.

let mut my_vector = Vec::with_capacity(10); // Pre-allocates space for 10 elements
for i in 0..10 {
    my_vector.push(i);
}

Accessing Elements

You can access elements in a vector using indexing, similar to arrays. However, vectors do not have built-in bounds checking. Accessing an element outside the valid range will cause a panic.

let numbers = vec![10, 20, 30, 40, 50];

let first_element = numbers[0]; // Access the first element (index 0)
println!("First element: {}", first_element);

// Safe access with `get()`: returns an `Option`
let maybe_element = numbers.get(10); // Index out of bounds
match maybe_element {
    Some(value) => println!("Element at index 10: {}", value),
    None => println!("Index out of bounds"),
}

Important:

  • Indexing ([]) is unsafe: It can panic if the index is out of bounds.
  • get() is safe: It returns an Option<T>, which is Some(T) if the index is valid and None otherwise. This allows you to handle out-of-bounds access gracefully.

Modifying Vectors

1. push(): Adds an element to the end of the vector.

let mut numbers = vec![1, 2, 3];
numbers.push(4);
println!("{:?}", numbers); // Output: [1, 2, 3, 4]

2. pop(): Removes and returns the last element of the vector. Returns None if the vector is empty.

let mut numbers = vec![1, 2, 3];
let last_element = numbers.pop();
println!("{:?}", numbers); // Output: [1, 2]
println!("{:?}", last_element); // Output: Some(3)

3. insert(): Inserts an element at a specific index. This shifts existing elements to make space.

let mut numbers = vec![1, 2, 3];
numbers.insert(1, 10); // Insert 10 at index 1
println!("{:?}", numbers); // Output: [1, 10, 2, 3]

4. remove(): Removes and returns the element at a specific index. This shifts subsequent elements to fill the gap.

let mut numbers = vec![1, 2, 3];
let removed_element = numbers.remove(1); // Remove element at index 1
println!("{:?}", numbers); // Output: [1, 3]
println!("{:?}", removed_element); // Output: 2

5. append(): Appends another vector to the end of the current vector. The other vector is consumed (moved).

let mut numbers1 = vec![1, 2, 3];
let numbers2 = vec![4, 5, 6];
numbers1.append(&mut numbers2); // numbers2 is moved into numbers1
println!("{:?}", numbers1); // Output: [1, 2, 3, 4, 5, 6]

6. truncate(): Resizes the vector, discarding elements beyond the new length.

let mut numbers = vec![1, 2, 3, 4, 5];
numbers.truncate(3);
println!("{:?}", numbers); // Output: [1, 2, 3]

Iterating over Vectors

You can iterate over vectors using various methods:

1. for loop (by value): Consumes the vector.

let numbers = vec![1, 2, 3];
for number in numbers {
    println!("{}", number);
} // numbers is no longer valid here

2. for loop (by reference): Iterates over the elements without consuming the vector.

let numbers = vec![1, 2, 3];
for number in &numbers {
    println!("{}", number);
}
println!("{:?}", numbers); // numbers is still valid

3. for loop (by mutable reference): Iterates over the elements with mutable access.

let mut numbers = vec![1, 2, 3];
for number in &mut numbers {
    *number *= 2; // Modify the element in place
}
println!("{:?}", numbers); // Output: [2, 4, 6]

4. iter(): Returns an iterator over immutable references.

5. iter_mut(): Returns an iterator over mutable references.

6. into_iter(): Returns an iterator that consumes the vector.

Other Useful Vector Methods

  • len(): Returns the number of elements in the vector.
  • is_empty(): Returns true if the vector is empty.
  • capacity(): Returns the current capacity of the vector.
  • clear(): Removes all elements from the vector.
  • sort(): Sorts the elements in place.
  • reverse(): Reverses the order of elements in place.
  • split_at(): Splits the vector into two vectors at a given index.
  • clone(): Creates a deep copy of the vector.

Example: Finding the Maximum Value

fn main() {
    let numbers = vec![5, 2, 9, 1, 5, 6];

    if numbers.is_empty() {
        println!("Vector is empty");
        return;
    }

    let mut max_value = numbers[0];

    for &number in &numbers {
        if number > max_value {
            max_value = number;
        }
    }

    println!("Maximum value: {}", max_value);
}

Summary

Vectors are a powerful and flexible data structure in Rust. Understanding how to create, manipulate, and iterate over vectors is essential for writing effective Rust programs. Remember to consider the trade-offs between safety (using get()) and performance (using indexing []) when accessing elements. Also, be mindful of ownership and borrowing rules when working with vectors and their elements.