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 anOption<T>, which isSome(T)if the index is valid andNoneotherwise. 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(): Returnstrueif 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.