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
- Create and initialize vectors using multiple methods
- Add, remove, and access vector elements safely
- Understand capacity vs length and memory management
- Iterate over vectors using different patterns
- Apply vectors to solve real-world problems
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.