Vectors

If you remember, the array is a fixed-size list of elements, of the same data type. Even with mut, its element count cannot be changed. A vector is kind of a re-sizable array but all elements must be in the same type.

πŸ’‘ Vec<T>: capital β€œV” as it’s a struct.

It’s a generic type, written as Vec<T>. T can have any type, ex. A vector of i32s is Vec<i32>. Also, Vectors always allocate their data in a dynamically allocated heap.

Creation

Empty Vector

let mut a = Vec::new(); // 1. With new() keyword
let mut b = vec![]; // 2. Using the vec! macro (πŸ’‘ usually create with values same time)

// ⭐️ If you need an immutable empty vector, you must have to specify the data type.
let a: Vec<i32> = Vec::new();
let b: Vec<String> = vec![];

With Type Annotations

let a: Vec<i32> = Vec::new();
let b: Vec<i32> = vec![];

let c = vec![1i32, 2, 3]; // Suffixing 1st value with data type

With Values

let a = vec![1, 2, 3];
let b: Vec<i32> = vec![1, 2, 3];
let c  = vec![1i32, 2, 3];  
let a = vec![0; 10]; // Ten zeroes
let b = vec![""; 10]; // Ten "" str

With a Capacity

let mut a: Vec<i32> = Vec::with_capacity(10);
println!("Length: {}, Capacity : {}", a.len(), a.capacity()); // Length: 0, Capacity : 10

πŸ’­ We’ll discuss this in the Length and Capacity.

Accessing and Changing Elements

By Index

We can access and change elements of a vector, via the index (like we access/ change elements of an array).

let mut a = vec![1, 2, 3];

println!("{} {} {}", a[0], a[1], a[2]); // 1 2 3

a[0] = 4; // [4, 2, 3]
(a[1], a[2]) = (a[2], a[1]); // Shuffle

println!("{:?}", a); // [4, 3, 2]

a[5] = 2; // πŸ’₯ panics at runtime; index out of bounds: the len is 3 but the index is 5

By the get Method

Similar to accessing elements via the index, but safer, as it always returns an Option<T>/ optional value.

let mut a = vec![1, 2, 3];

let x = a.get(0); // Some(1)
let y = a.get(5); // None

πŸ’­ Option<T> can be either Some value or None (no value). It is also a generic type, like Vec<T>. We’ll discuss more details in the Generics: Option. For the moment, focus on vectors.

By the push and pop Methods

let mut a: Vec<i32> = Vec::new();

a.push(1); // Add 1 to the end; a = [1]
a.push(2); // Add 2 to the end; a = [1, 2]

a.pop(); // Remove 2 from the end; a = [1]

let x = a.pop(); // Remove 1 from the end and assign it to x as Option<T>; a = []
// x = Some(1)

let y = a.pop(); // Remove nothing as a is empty; a = [] ⭐️ No panics
// y = None

Length and Capacity

In Rust, most types have a fixed size known at compile time and implement the trait Sized. Vec<T> is also a sized type; A struct that internally stores,

  1. A pointer: points to the heap-allocated memory storing the elements contiguously, like a slice [T]
  2. Length: NO of elements currently have
  3. Capacity: Amount of space allocated for any future elements

⭐️ If the length of a vector exceeds its capacity, its capacity will be increased automatically. But its elements will be reallocated(which can be slow). So, always use Vec::with_capacity whenever it’s possible.

let mut e: Vec<i32> = Vec::with_capacity(10); // Length: 0, Capacity : 10

// These are all done without reallocating...
for i in 0..10 {
    e.push(i);
}

// ...but this may make the vector reallocate as exceeded current capacity
e.push(11);

πŸ”Ž Dynamically sized types (DSTs)/ unsized types don’t have a fixed size at compile time, and the size is known only at run-time. Slices and trait objects are two examples of DSTs.

πŸ‘¨β€πŸ« Before going to the next…

  • πŸ’― Vectors can be used with iterators in three ways,

    let mut a = vec![1, 2, 3, 4, 5];
    
    for i in a {
        println!("Take ownership of the vector and its element {}", i);
    }
    
    for i in &a {
        println!("A reference to {}", i);
    }
    
    for i in &mut a {
        println!("A mutable reference to {}", i);
    }
    
  • πŸ’― The String/ &str data types are UTF-8 encoded vectors. But you can not index into a String because of encoding.

    ⭐️ We can iterate over the characters of a string via the chars() method. But for more accurate results, you should use a crate like unicode_segmentation that follows more accurate Unicode text segmentation standards.

    let a = String::from("Hello!");
    print!("{}", a.chars().count()); // 6
    // πŸ’‘ H   e   l   l   o   !
    
    let a = "a̐éö̲\r\n";
    for (i, v) in a.chars().enumerate() {
        println!("{i}: {v}");
    }
    
    // 0: a
    // 1: ̐
    // 2: Γ©
    // 3: ΓΆ
    // 4: Μ²
    // 5:  
    // 6: