Understanding Arrays and Vectors in Rust
Rust provides several ways to represent sequences of values in memory. In this blog post, we'll explore two fundamental types: Arrays and Vectors. We'll dive into their characteristics, use cases, and how to work with them effectively.
Arrays in Rust
Arrays in Rust are fixed-size sequences of elements of the same type. They're denoted by the type [T; N]
, where T
is the type of elements and N
is the number of elements.
Key Characteristics of Arrays:
- Fixed size, determined at compile time
- Cannot be resized after creation
- Stored on the stack
Let's look at some examples:
// Creating arrays
let lazy_caterer: [u32; 6] = [1, 2, 4, 7, 11, 16];
let taxonomy = ["Animalia", "Anthropoda", "Insecta"];
// Accessing elements
assert_eq!(lazy_caterer[3], 7);
assert_eq!(taxonomy.len(), 3);
// Creating an array with repeated values
let mystring = ["akbar"; 10];
for n in mystring {
println!("{}", n);
}
Arrays are particularly useful for creating fixed-size buffers:
// Creating a 1KB buffer filled with zeros
let mut buffer = [0u8; 1024];
// Copying data into the buffer
let message = b"Hello rust buffer";
buffer[..message.len()].copy_from_slice(message);
println!("{}", std::str::from_utf8(&buffer[..buffer.len()]).unwrap());
Array Slices
Rust also provides slice types &[T]
and &mut [T]
, which are references to a portion of an array or vector. Slices are useful for working with a subset of elements without copying them.
// Using slices to manipulate part of the buffer
for i in 0..10 {
buffer[message.len() + i] = i as u8;
}
println!("Counter values {:?}", &buffer[message.len()..message.len() + 10]);
Vectors in Rust
Vectors (Vec<T>
) are dynamically-sized arrays. Unlike arrays, vectors can grow or shrink in size and are stored on the heap.
Key Characteristics of Vectors:
- Dynamically sized
- Can be resized at runtime
- Stored on the heap
Let's explore various ways to create and manipulate vectors:
// Creating vectors
let mut primes = vec![2, 3, 5, 7];
assert_eq!(primes.iter().product::<i32>(), 210);
// Creating a vector with repeated values
fn new_pixel_buffer(rows: usize, cols: usize) -> Vec<u8> {
vec![0; rows * cols]
}
// Building a vector by pushing elements
let mut pal = Vec::new();
pal.push("step");
pal.push("on");
pal.push("no");
pal.push("pets");
assert_eq!(pal, ["step", "on", "no", "pets"]);
// Creating a vector from an iterator
let v: Vec<i32> = (0..5).collect();
assert_eq!(v, [0, 1, 2, 3, 4]);
Vector Capacity and Reallocation
Vectors manage their own memory and can grow as needed. However, growing a vector may involve reallocating memory, which can be costly. To optimize performance, you can pre-allocate capacity:
let mut v: Vec<i32> = Vec::with_capacity(2);
assert_eq!(v.len(), 0);
assert_eq!(v.capacity(), 2);
v.push(1);
v.push(2);
v.push(3);
println!("Capacity after pushing 3 elements: {}", v.capacity()); // capacity is 4, len is 3
Modifying Vectors
Vectors provide methods for inserting and removing elements:
let mut v = vec![10, 20, 30, 40, 50];
// Inserting an element
v.insert(3, 35);
assert_eq!(v, [10, 20, 30, 35, 40, 50]);
// Removing an element
v.remove(1);
assert_eq!(v, [10, 30, 35, 40, 50]);
// Popping elements from the end
let mut v = vec!["Snow Puff", "Glass Gem"];
assert_eq!(v.pop(), Some("Glass Gem"));
assert_eq!(v.pop(), Some("Snow Puff"));
assert_eq!(v.pop(), None);
Conclusion
Arrays and Vectors are fundamental to working with sequences of data in Rust. Arrays offer fixed-size, stack-allocated storage, while Vectors provide flexibility with dynamic sizing and heap allocation. Understanding when to use each and how to manipulate them effectively is crucial for writing efficient Rust code.
Remember:
- Use arrays when you need a fixed number of elements and want stack allocation.
- Use vectors when you need a dynamic size or don't know the size at compile time.
- Leverage slices to work with portions of arrays or vectors efficiently.
By mastering these concepts, you'll be well-equipped to handle various data storage and manipulation tasks in your Rust programs.