Rust - Vectors (Vec)

Overview

Estimated time: 50–70 minutes

Master Rust's Vec<T> - the most commonly used collection type. Learn creation, manipulation, iteration, and advanced patterns for dynamic arrays that can grow and shrink at runtime.

Learning Objectives

Prerequisites

Creating Vectors

Empty Vector

fn main() {
    // Empty vector with explicit type
    let mut numbers: Vec<i32> = Vec::new();
    println!("Empty vector: {:?}", numbers);
    
    // Alternative using vec! macro
    let mut scores: Vec<i32> = vec![];
    println!("Empty vector with macro: {:?}", scores);
}

Expected output:

Empty vector: []
Empty vector with macro: []

Vector with Initial Values

fn main() {
    // Vector with initial values
    let numbers = vec![1, 2, 3, 4, 5];
    println!("Numbers: {:?}", numbers);
    
    // Vector with repeated values
    let zeros = vec![0; 5]; // Five zeros
    println!("Zeros: {:?}", zeros);
    
    // String vector
    let names = vec!["Alice", "Bob", "Charlie"];
    println!("Names: {:?}", names);
}

Expected output:

Numbers: [1, 2, 3, 4, 5]
Zeros: [0, 0, 0, 0, 0]
Names: ["Alice", "Bob", "Charlie"]

Adding Elements

Push Method

fn main() {
    let mut fruits = Vec::new();
    
    // Add elements one at a time
    fruits.push("apple");
    fruits.push("banana");
    fruits.push("orange");
    
    println!("Fruits: {:?}", fruits);
    println!("Length: {}", fruits.len());
}

Expected output:

Fruits: ["apple", "banana", "orange"]
Length: 3

Insert and Extend

fn main() {
    let mut numbers = vec![1, 3, 5];
    
    // Insert at specific index
    numbers.insert(1, 2); // Insert 2 at index 1
    println!("After insert: {:?}", numbers);
    
    // Extend with another vector
    let more_numbers = vec![6, 7, 8];
    numbers.extend(more_numbers);
    println!("After extend: {:?}", numbers);
    
    // Extend with iterator
    numbers.extend(9..12); // Add 9, 10, 11
    println!("After range extend: {:?}", numbers);
}

Expected output:

After insert: [1, 2, 3, 5]
After extend: [1, 2, 3, 5, 6, 7, 8]
After range extend: [1, 2, 3, 5, 6, 7, 8, 9, 10, 11]

Accessing Elements

Index Access vs Get Method

fn main() {
    let numbers = vec![10, 20, 30, 40, 50];
    
    // Index access (panics if out of bounds)
    println!("First element: {}", numbers[0]);
    println!("Third element: {}", numbers[2]);
    
    // Safe access with get method
    match numbers.get(2) {
        Some(value) => println!("Element at index 2: {}", value),
        None => println!("No element at index 2"),
    }
    
    // Accessing out of bounds safely
    match numbers.get(10) {
        Some(value) => println!("Element at index 10: {}", value),
        None => println!("No element at index 10"),
    }
}

Expected output:

First element: 10
Third element: 30
Element at index 2: 30
No element at index 10

First and Last Elements

fn main() {
    let numbers = vec![1, 2, 3, 4, 5];
    
    // First and last elements
    if let Some(first) = numbers.first() {
        println!("First: {}", first);
    }
    
    if let Some(last) = numbers.last() {
        println!("Last: {}", last);
    }
    
    // Empty vector
    let empty: Vec<i32> = vec![];
    println!("Empty first: {:?}", empty.first());
    println!("Empty last: {:?}", empty.last());
}

Expected output:

First: 1
Last: 5
Empty first: None
Empty last: None

Removing Elements

Pop and Remove

fn main() {
    let mut colors = vec!["red", "green", "blue", "yellow"];
    println!("Original: {:?}", colors);
    
    // Remove last element
    if let Some(popped) = colors.pop() {
        println!("Popped: {}", popped);
    }
    println!("After pop: {:?}", colors);
    
    // Remove at specific index
    let removed = colors.remove(1); // Remove "green"
    println!("Removed: {}", removed);
    println!("After remove: {:?}", colors);
    
    // Remove and replace with last element (faster)
    let swapped = colors.swap_remove(0); // Remove "red", replace with last
    println!("Swap removed: {}", swapped);
    println!("After swap_remove: {:?}", colors);
}

Expected output:

Original: ["red", "green", "blue", "yellow"]
Popped: yellow
After pop: ["red", "green", "blue"]
Removed: green
After remove: ["red", "blue"]
Swap removed: red
After swap_remove: ["blue"]

Capacity and Memory Management

Understanding Capacity

fn main() {
    let mut numbers = Vec::new();
    println!("Initial - Length: {}, Capacity: {}", numbers.len(), numbers.capacity());
    
    // Add elements and watch capacity grow
    for i in 1..=5 {
        numbers.push(i);
        println!("After pushing {}: Length: {}, Capacity: {}",
            i, numbers.len(), numbers.capacity());
    }
    
    // Pre-allocate capacity
    let mut optimized = Vec::with_capacity(10);
    println!("Pre-allocated - Length: {}, Capacity: {}",
        optimized.len(), optimized.capacity());
    
    // Reserve additional capacity
    numbers.reserve(10);
    println!("After reserve: Length: {}, Capacity: {}", 
        numbers.len(), numbers.capacity());
}

Expected output (capacity may vary):

Initial - Length: 0, Capacity: 0
After pushing 1: Length: 1, Capacity: 4
After pushing 2: Length: 2, Capacity: 4
After pushing 3: Length: 3, Capacity: 4
After pushing 4: Length: 4, Capacity: 4
After pushing 5: Length: 5, Capacity: 8
Pre-allocated - Length: 0, Capacity: 10
After reserve: Length: 5, Capacity: 15

Iterating Over Vectors

Different Iteration Patterns

fn main() {
    let numbers = vec![1, 2, 3, 4, 5];
    
    // By reference (most common)
    println!("By reference:");
    for num in &numbers {
        println!("  {}", num);
    }
    
    // By value (consumes the vector)
    let numbers2 = vec![10, 20, 30];
    println!("By value:");
    for num in numbers2 {
        println!("  {}", num);
    }
    // numbers2 is no longer accessible here
    
    // With indices
    println!("With indices:");
    for (index, value) in numbers.iter().enumerate() {
        println!("  Index {}: {}", index, value);
    }
    
    // Mutable iteration
    let mut scores = vec![85, 90, 78, 92];
    println!("Before modification: {:?}", scores);
    for score in &mut scores {
        *score += 5; // Add 5 points to each score
    }
    println!("After modification: {:?}", scores);
}

Expected output:

By reference:
  1
  2
  3
  4
  5
By value:
  10
  20
  30
With indices:
  Index 0: 1
  Index 1: 2
  Index 2: 3
  Index 3: 4
  Index 4: 5
Before modification: [85, 90, 78, 92]
After modification: [90, 95, 83, 97]

Vector Methods and Operations

Common Vector Methods

fn main() {
    let mut numbers = vec![3, 1, 4, 1, 5, 9, 2, 6];
    println!("Original: {:?}", numbers);
    
    // Check if empty
    println!("Is empty: {}", numbers.is_empty());
    
    // Contains check
    println!("Contains 5: {}", numbers.contains(&5));
    println!("Contains 10: {}", numbers.contains(&10));
    
    // Sort the vector
    numbers.sort();
    println!("Sorted: {:?}", numbers);
    
    // Reverse
    numbers.reverse();
    println!("Reversed: {:?}", numbers);
    
    // Deduplicate (remove consecutive duplicates)
    numbers.dedup();
    println!("After dedup: {:?}", numbers);
    
    // Clear all elements
    numbers.clear();
    println!("After clear: {:?}", numbers);
    println!("Length after clear: {}", numbers.len());
}

Expected output:

Original: [3, 1, 4, 1, 5, 9, 2, 6]
Is empty: false
Contains 5: true
Contains 10: false
Sorted: [1, 1, 2, 3, 4, 5, 6, 9]
Reversed: [9, 6, 5, 4, 3, 2, 1, 1]
After dedup: [9, 6, 5, 4, 3, 2, 1]
After clear: []
Length after clear: 0

Splitting and Slicing

fn main() {
    let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8];
    
    // Get slices
    let first_half = &numbers[0..4];
    let second_half = &numbers[4..];
    println!("First half: {:?}", first_half);
    println!("Second half: {:?}", second_half);
    
    // Split at index
    let (left, right) = numbers.split_at(3);
    println!("Split at 3 - Left: {:?}, Right: {:?}", left, right);
    
    // Chunks
    println!("In chunks of 3:");
    for chunk in numbers.chunks(3) {
        println!("  {:?}", chunk);
    }
    
    // Windows (sliding)
    println!("Windows of size 3:");
    for window in numbers.windows(3) {
        println!("  {:?}", window);
    }
}

Expected output:

First half: [1, 2, 3, 4]
Second half: [5, 6, 7, 8]
Split at 3 - Left: [1, 2, 3], Right: [4, 5, 6, 7, 8]
In chunks of 3:
  [1, 2, 3]
  [4, 5, 6]
  [7, 8]
Windows of size 3:
  [1, 2, 3]
  [2, 3, 4]
  [3, 4, 5]
  [4, 5, 6]
  [5, 6, 7]
  [6, 7, 8]

Practical Examples

Grade Calculator

fn main() {
    let mut grades = vec![85.5, 92.0, 78.5, 96.0, 87.5, 91.0];
    
    // Calculate average
    let sum: f64 = grades.iter().sum();
    let average = sum / grades.len() as f64;
    println!("Average grade: {:.2}", average);
    
    // Find highest and lowest
    let highest = grades.iter()
        .max_by(|a, b| a.partial_cmp(b).unwrap())
        .unwrap();
    let lowest = grades.iter()
        .min_by(|a, b| a.partial_cmp(b).unwrap())
        .unwrap();
    
    println!("Highest: {:.1}", highest);
    println!("Lowest: {:.1}", lowest);
    
    // Apply curve (add 3 points to each grade)
    for grade in &mut grades {
        *grade += 3.0;
        if *grade > 100.0 {
            *grade = 100.0; // Cap at 100
        }
    }
    
    println!("Grades after curve: {:?}", grades);
    
    // Count grades above 90
    let high_grades = grades.iter().filter(|&&grade| grade >= 90.0).count();
    println!("Grades 90 or above: {}", high_grades);
}

Expected output:

Average grade: 88.42
Highest: 96.0
Lowest: 78.5
Grades after curve: [88.5, 95.0, 81.5, 99.0, 90.5, 94.0]
Grades 90 or above: 4

Todo List Manager

fn main() {
    let mut todo_list: Vec<String> = Vec::new();
    
    // Add tasks
    todo_list.push("Buy groceries".to_string());
    todo_list.push("Walk the dog".to_string());
    todo_list.push("Finish Rust tutorial".to_string());
    todo_list.push("Call mom".to_string());
    
    // Display list
    println!("šŸ“ Todo List:");
    for (index, task) in todo_list.iter().enumerate() {
        println!("  {}. {}", index + 1, task);
    }
    
    // Mark task as complete (remove)
    if todo_list.len() > 1 {
        let completed = todo_list.remove(1); // Remove "Walk the dog"
        println!("\nāœ… Completed: {}", completed);
    }
    
    // Add urgent task at beginning
    todo_list.insert(0, "Pay bills (URGENT)".to_string());
    
    // Display updated list
    println!("\nšŸ“ Updated Todo List:");
    for (index, task) in todo_list.iter().enumerate() {
        println!("  {}. {}", index + 1, task);
    }
    
    println!("\nRemaining tasks: {}", todo_list.len());
}

Expected output:

šŸ“ Todo List:
  1. Buy groceries
  2. Walk the dog
  3. Finish Rust tutorial
  4. Call mom

āœ… Completed: Walk the dog

šŸ“ Updated Todo List:
  1. Pay bills (URGENT)
  2. Buy groceries
  3. Finish Rust tutorial
  4. Call mom

Remaining tasks: 4

Common Pitfalls

Index Out of Bounds

fn main() {
    let numbers = vec![1, 2, 3];
    
    // This will panic!
    // println!("{}", numbers[10]);
    
    // Safe alternative
    match numbers.get(10) {
        Some(value) => println!("Value: {}", value),
        None => println!("Index 10 is out of bounds"),
    }
}

Borrowing While Modifying

fn main() {
    let mut numbers = vec![1, 2, 3, 4, 5];
    
    // This won't compile - can't borrow as immutable while borrowed as mutable
    // let first = &numbers[0];
    // numbers.push(6);
    // println!("First: {}", first);
    
    // Correct approach - limit scope of borrow
    {
        let first = &numbers[0];
        println!("First: {}", first);
    } // Borrow ends here
    
    numbers.push(6); // Now we can modify
    println!("Numbers: {:?}", numbers);
}

Performance Tips

Pre-allocate When Possible

fn main() {
    // Inefficient - multiple reallocations
    let mut numbers1 = Vec::new();
    for i in 0..1000 {
        numbers1.push(i);
    }
    
    // Efficient - single allocation
    let mut numbers2 = Vec::with_capacity(1000);
    for i in 0..1000 {
        numbers2.push(i);
    }
    
    // Even more efficient for simple cases
    let numbers3: Vec<i32> = (0..1000).collect();
    
    println!("All vectors have length: {}, {}, {}", 
        numbers1.len(), numbers2.len(), numbers3.len());
}

Checks for Understanding

Question 1: Vector Operations

What will this code output?

fn main() {
    let mut scores = vec![95, 87, 92];
    scores.push(88);
    scores.insert(1, 90);
    println!("{:?}", scores);
}
Show Answer

Answer: [95, 90, 87, 92, 88]

The insert(1, 90) places 90 at index 1, shifting existing elements right.

Question 2: Safe Access

Why should you prefer vec.get(index) over vec[index] in many cases?

Show Answer

Answer: get() returns an Option and never panics, while indexing with [] will panic if the index is out of bounds. This makes get() safer for cases where the index might be invalid.

Question 3: Memory Efficiency

What's the difference between Vec::new() and Vec::with_capacity(n)?

Show Answer

Answer: Vec::new() creates an empty vector with zero capacity, while Vec::with_capacity(n) pre-allocates space for n elements. Using with_capacity when you know the approximate size can prevent multiple reallocations as the vector grows.