RustBrock/Vector.md
2025-01-17 20:51:38 -07:00

5.4 KiB

Vector

Vectors allow you to store more and one value in a single data structure that puts all of the values next to each other in the memory

Vectors can only store values of the same type hence the generic in the Vec<T> keyword

useful when you have a list of items, like the lines of text in a file or the prices of items in a shopping cart (this would be better with a hash map (more obj relation))

Creating a new Vector

to create a empty vector we call

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

Type annotation is need because a value is not given here and the compiler will not know what type of data will be stored here

hence why the generic must be specified instead of inferred

More often a Vector will have initial values and Rust will infer the type of value stored

Rust has a Macro for this vec! this will create a new vector that holds the values given to it

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

this is implicitly a Vec<i32>

Updating a Vector

Add values to the vector you can use the push method

let mut v = Vec::new();

v.push(5);
v.push(6);
v.push(7);
v.push(8);

This must be mutable in order to do this this is another way to implicitly start up a Vec<i32> variable

Reading Elements of Vectors

There a two ways to reference a value sorted in a vector

via indexing or by using the get method

here is an example of using both

    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."),
    }

this uses the index of 2 to get the third element vectors are indexed starting at zero

using & and [] gives us a reference to the element at the index value

when using get it returns an Option<&T> which is a referenced generic which is then specified to a i32

as you can see the get method is more safe because it accounts for when it way be null value or out of range

let does_not_exist = &v[100];
let does_not_exist = v.get(100);

the first one would cause the program to panic but the second wouldn't cause a panic instead it would require the situation to be dealt with in a match pattern for when it is None

see [Option<T>](data_types.md#The Option Enum and Advantages over Null types) for more details

when the program has a valid reference the borrow checker ensures this reference and any other references must be valid to the contents of where it refers to.

One case where this could easily be broken is creating a reference then trying to add an element to the end then trying to use the reference later in the function

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

    let first = &v[0];

    v.push(6);

    println!("The first element is: {first}");

this would result in a borrow checker error because vector.push uses a mutable reference to the vector check here for more details ownership#Rules

This is also due to how vectors are implemented, what happens when the allocated space on memory runs out, then it must be copied to another place in memory, then the old vector is freed

In that case where the reference is pointing to may be deallocated and therefore not valid

For implementation details of Vec<T> type see The Rustonomicon

Iterating Over the Values in a Vector

We would rather iterate over the elements rather than over the index ands access each one one at a time here is an example that give an immutable reference for each element

    let v = vec![100, 32, 57];
    for i in &v {
        println!("{i}");
    }

can also iterate over mutable referneces here is an example where we add 50 to each element using the * or dereference operator to assign values to the reference

    let mut v = vec![100, 32, 57];
    for i in &mut v {
        *i += 50;
    }

We cannot add or remove from the vector when iterating over it because of borrow checking rules, the reference to the whole vector prevents that

Using an Enum to Store Multiple Types

Vectors can only store values that are of the same type, this can be inconvenient sometimes.

But you can use variants of an enum which is considered the same type

this is allowed

    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 to store per element in the vector so it must be specific when initializing a vector

using an enum plus a match ensures that all possible cases are handled which ensures that a panic will not happen

If you do not know the exhaustive set of types a program will get at runtime to store in a vector then the enum technique will not work instead use a trait object

Here is a the API documentation for a Vector with all of the methods defined on a Vec<T>

For example a pop removes and returns the last element

Dropping a Vector Drops Its Elements

This is like any other struct, when a vector is freed/goes out of scope then so will its inner elements