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

156 lines
5.4 KiB
Markdown

# 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
```rust
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
```rust
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
```rust
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
```rust
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
<img style="height:5svh" src="https://doc.rust-lang.org/book/img/ferris/does_not_compile.svg" />
```rust
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](https://doc.rust-lang.org/nomicon/vec/vec.html)
## 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
```rust
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
```rust
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
```rust
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