Rust Vector - rFronteddu/general_wiki GitHub Wiki

Vec, also known as a vector allows you to store more than one value in a single data structure that puts all the values next to each other in memory. Vectors can only store values of the same type.

Creating a new Vector

To create a new empty vector, we call the Vec::new function:

let v: Vec<i32> = Vec::new();

More often, you’ll create a Vec with initial values and Rust will infer the type of value you want to store, so you rarely need to do this type annotation. Rust conveniently provides the vec! macro, which will create a new vector that holds the values you give it.

let v = vec![1, 2, 3];

Because we’ve given initial i32 values, Rust can infer that the type of v is Vec, and the type annotation isn’t necessary. Next, we’ll look at how to modify a vector.

Updating a Vector

To create a vector and then add elements to it, we can use the push method:

    let mut v = Vec::new();

    v.push(5);

As with any variable, if we want to be able to change its value, we need to make it mutable using the mut keyword.

Reading Elements of Vectors

There are two ways to reference a value stored in a vector: via indexing or by using the get method.

let v = vec![1, 2, 3, 4, 5];

let third: &i32 = &v[2];
println!("The third element is {third}");

let third: Option<&i32> = v.get(2);
match third {
    Some(third) => println!("The third element is {third}"),
    None => println!("There is no third element."),
}

Note a few details here. We use the index value of 2 to get the third element because vectors are indexed by number, starting at zero. Using & and [] gives us a reference to the element at the index value. When we use the get method with the index passed as an argument, we get an Option<&T> that we can use with match.

Rust provides these two ways to reference an element so you can choose how the program behaves when you try to use an index value outside the range of existing elements.

Recall the rule that states you can’t have mutable and immutable references in the same scope. That rule applies in Listing below, where we hold an immutable reference to the first element in a vector and try to add an element to the end. This program won’t work if we also try to refer to that element later in the function.

    let mut v = vec![1, 2, 3, 4, 5];

    let first = &v[0]; // immutable borrow occurs here

    v.push(6); // mutable borrows occurs here - error: cannot borrow as mutable because also borrowed as immutable

    println!("The first element is: {first}"); // immutable borrows occurs here

This error is due to the way vectors work: because vectors put the values next to each other in memory, adding a new element onto the end of the vector might require allocating new memory and copying the old elements to the new space, if there isn’t enough room to put all the elements next to each other where the vector is currently stored. In that case, the reference to the first element would be pointing to deallocated memory. The borrowing rules prevent programs from ending up in that situation.

Iterating Over the Values in a Vector

To access each element in a vector in turn, we would iterate through all of the elements rather than use indices to access one at a time.

    let v = vec![100, 32, 57];
    for n_ref in &v {
        // n_ref has type &i32
        let n_plus_one: i32 = *n_ref + 1;
        println!("{n_plus_one}");
    }

We can also iterate over mutable references to each element in a mutable vector in order to make changes to all the elements.

    let mut v = vec![100, 32, 57];
    for n_ref in &mut v {
        // n_ref has type &mut i32
        *n_ref += 50;
    }

To change the value that the mutable reference refers to, we again use the * dereference operator to get to the value in n_ref before we can use the += operator.

Rust does not allow you to add or remove elements from the vector during iteration.

Using an Enum to Store Multiple Types

Vectors can only store values that are of the same type. This can be inconvenient; there are definitely use cases for needing to store a list of items of different types. Fortunately, the variants of an enum are defined under the same enum type, so when we need one type to represent elements of different types, we can define and use an enum!

For example, say we want to get values from a row in a spreadsheet in which some of the columns in the row contain integers, some floating-point numbers, and some strings. We can define an enum whose variants will hold the different value types, and all the enum variants will be considered the same type: that of the enum. Then we can create a vector to hold that enum and so, ultimately, hold different types.

    enum SpreadsheetCell {
        Int(i32),
        Float(f64),
        Text(String),
    }

    let row = vec![
        SpreadsheetCell::Int(3),
        SpreadsheetCell::Text(String::from("blue")),
        SpreadsheetCell::Float(10.12),
    ];

Rust needs to know what types will be in the vector at compile time so it knows exactly how much memory on the heap will be needed to store each element. We must also be explicit about what types are allowed in this vector. If you don’t know the exhaustive set of types a program will get at runtime to store in a vector, the enum technique won’t work. Instead, you can use a trait object.

⚠️ **GitHub.com Fallback** ⚠️