DEV Community

Cover image for Day 16: Journey into Rust Collections - Mastering the Vector
Aniket Botre
Aniket Botre

Posted on

Day 16: Journey into Rust Collections - Mastering the Vector

If you need to work with different kinds of data in your programs, you can rely on Rust's standard collection library. It offers you the best general-purpose data structures that are optimized for performance and compatibility. You can easily share data between libraries without having to convert them, thanks to the standard implementations.

In the vast landscape of Rust collections, vectors emerge as dynamic arrays, offering a blend of flexibility and performance. Let's delve into the intricacies of Rust vectors, understanding their nuances and mastering the art of wielding them in your code.


Understanding Vectors in Rust

Vectors in Rust are dynamic data structures that are designed to store multiple values of the same data type efficiently in memory. They are similar to arrays in other programming languages but with the added ability to grow or shrink in size as needed. Vectors are implemented as Vec<T>, a generic type that can hold elements of any specified type T. This means that if you declare a vector to store integers, it can only contain integers and not any other type of data.

Note: We can also say that vector is a resizable array.

  • A Vector can grow or shrink at runtime.

  • A Vector is a homogeneous collection.

  • Every element in a Vector is assigned a unique index number. The index starts from 0 and goes up to n-1 where, n is the size of the collection.

  • A Vector will can append values to the end. In other words, a Vector can be used to implement a stack.

  • Memory for a Vector is allocated in the heap.


Memory Allocation and Structure

Vectors in Rust are stored on the heap, which means they are allocated at runtime and their size can be adjusted dynamically. A vector is represented using three parameters: a pointer to the data, its length, and its capacity. The capacity indicates how much memory is reserved for the vector, and as long as the length is smaller than the capacity, the vector can grow without reallocating memory. However, when the length exceeds the capacity, Rust automatically reallocates the vector with a larger capacity, typically doubling it to maintain efficiency.


Creating and Initializing Vectors

  • Using the vec! macro to create a vector with predefined values:
fn main() {
   let v = vec![1,2,3];
   println!("{:?}",v);
   //Output: [1, 2, 3]
}
Enter fullscreen mode Exit fullscreen mode
  • Calling Vec::new() to create an empty vector, which can later be populated using methods like push:
fn main() {
   let mut v = Vec::new();
   v.push(20);
   v.push(30);
   v.push(40);

   println!("size of vector is :{}",v.len());
   //Output: size of vector is :3
   println!("{:?}",v);
   //Output: [20, 30, 40]
}
Enter fullscreen mode Exit fullscreen mode
  • Specifying a capacity with Vec::with_capacity(10) to optimize memory allocation if the expected size is known:
fn main(){
  let mut vector: Vec<i32> = Vec::with_capacity(100);

  for i in 101..=150{
    vector4.push(i);
  }

  println!("Length of vector is {}, but it can hold {} elements", vector4.len(), vector4.capacity());
  //Output: Length of vector is 50, but it can hold 100 elements
}
Enter fullscreen mode Exit fullscreen mode

Accessing and Modifying Vector Elements

We will explore some common/most used methods:

  • push(): Appends an element to the end of a collection.
fn main() {
   let mut v = Vec::new();
   v.push(20);
   v.push(30);
   v.push(40);

   println!("{:?}",v);
   // Output: [20, 30, 40]
}
Enter fullscreen mode Exit fullscreen mode
  • pop(): Removes the last element from a vector and returns it.
fn main() {
   let mut v = Vec::new();
   v.push(20);
   v.push(30);
   v.pop(40);

   println!("{:?}",v);
   // Output: [20, 30]
}
Enter fullscreen mode Exit fullscreen mode
  • remove(): Removes and returns the element at position index within the vector, shifting all elements after it to the left.
fn main() {
   let mut v = vec![10,20,30];
   v.remove(1);
   println!("{:?}",v);
   // Output: [10, 30]
}
Enter fullscreen mode Exit fullscreen mode
  • contains(): Returns true if the slice contains an element with the given value.
fn main() {
   let v = vec![10,20,30];
   if v.contains(&10) {
      println!("found 10");
      // Output: found 10
   }
   println!("{:?}",v);
   // Output: [10, 20, 30]
}
Enter fullscreen mode Exit fullscreen mode
  • len(): Returns the number of elements in the vector, also referred to as its 'length'.
fn main() {
   let v = vec![1,2,3];
   println!("size of vector is :{}",v.len());
   // Output: size of vector is :3
}
Enter fullscreen mode Exit fullscreen mode

Individual elements in a vector can be accessed using their corresponding index numbers. The following example creates a vector ad prints the value of the first element.

fn main() {
   let mut v = Vec::new();
   v.push(20);
   v.push(30);

   println!("{:?}",v[0]);
  //Output: 20
}
Enter fullscreen mode Exit fullscreen mode

Memory Management and Safety

Rust ensures memory safety through its ownership and borrowing rules. When a vector goes out of scope, it and all its elements are dropped, freeing the memory. Rust is very opinionated about safety, and certain operations, such as accessing elements using pointer arithmetic, must be wrapped in an unsafe block.


Conclusion

Rust's vectors provide a rich set of functionalities, ensuring optimal performance and safety. Exploring these dynamic powerhouses opens doors to efficient data handling in your Rust journey. πŸš€πŸ’»

Stay tuned for more Rust adventures! πŸŽ‰ #RustLang #CollectionsInRust #Day16

Top comments (0)