Day 16: Journey into Rust Collections - Mastering the Vector
Tags: rust, 100daysofcode, learning, programming
Published: 2024-01-16 19:40:00

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:

```rust
fn main() {
   let v = vec![1,2,3];
   println!("{:?}",v);
   //Output: [1, 2, 3]
}
```

- Calling Vec::new() to create an empty vector, which can later be populated using methods like push:

```rust
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]
}
```

- Specifying a capacity with Vec::with_capacity(10) to optimize memory allocation if the expected size is known:

```rust
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
}
```

---

## Accessing and Modifying Vector Elements

We will explore some common/most used methods:

- `push()`: Appends an element to the end of a collection.

```rust
fn main() {
   let mut v = Vec::new();
   v.push(20);
   v.push(30);
   v.push(40);
   
   println!("{:?}",v);
   // Output: [20, 30, 40]
}
```

- `pop()`: Removes the last element from a vector and returns it.

```rust
fn main() {
   let mut v = Vec::new();
   v.push(20);
   v.push(30);
   v.pop(40);
   
   println!("{:?}",v);
   // Output: [20, 30]
}
```

- `remove()`: Removes and returns the element at position index within the vector, shifting all elements after it to the left.

```rust
fn main() {
   let mut v = vec![10,20,30];
   v.remove(1);
   println!("{:?}",v);
   // Output: [10, 30]
}
```

- `contains()`: Returns true if the slice contains an element with the given value.

```rust
fn main() {
   let v = vec![10,20,30];
   if v.contains(&10) {
      println!("found 10");
      // Output: found 10
   }
   println!("{:?}",v);
   // Output: [10, 20, 30]
}
```

- `len()`: Returns the number of elements in the vector, also referred to as its 'length'.

```rust
fn main() {
   let v = vec![1,2,3];
   println!("size of vector is :{}",v.len());
   // Output: size of vector is :3
}
```

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.

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

   println!("{:?}",v[0]);
  //Output: 20
}
```

---

## 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