A vector in Rust is a dynamic collection that stores multiple values of the same type in contiguous memory.
If an array was a box with fixed slots, a Vec<T> is a box that can grow when we need to put more things in it. That’s why it’s one of the collections you’ll use the most.
Creating a vector
We can create an empty vector with Vec::new():
fn main() {
let mut numbers: Vec<i32> = Vec::new();
}Here we explicitly set the type because the vector is empty and Rust can’t guess what we’re going to store inside.
We can also use the vec! macro, which is the most convenient when we already have values:
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
}In this case Rust infers that numbers is a Vec<i32>.
Adding elements
To add values we use push.
fn main() {
let mut tasks = Vec::new();
tasks.push("Buy bread");
tasks.push("Learn Rust");
tasks.push("Don't fight with the borrow checker");
}Notice the mut. A vector can grow, but only if the variable holding it is mutable.
Accessing elements
We have two main ways to access an element.
The first is with indices:
fn main() {
let numbers = vec![10, 20, 30];
let second = numbers[1];
println!("{}", second);
}This is straightforward, but if the index doesn’t exist, the program will panic!.
The second way is with get, which returns an Option:
fn main() {
let numbers = vec![10, 20, 30];
match numbers.get(10) {
Some(value) => println!("Value: {}", value),
None => println!("That index doesn't exist"),
}
}get is safer when the index might come from outside, like user input or an API.
If you control the index and know it exists, using [] is fine. If the index comes from outside, get is usually a better choice.
Iterating over a vector
We can iterate without consuming it by using references:
fn main() {
let numbers = vec![1, 2, 3];
for number in &numbers {
println!("{}", number);
}
}If we want to modify the values, we use mutable references:
fn main() {
let mut numbers = vec![1, 2, 3];
for number in &mut numbers {
*number *= 2;
}
println!("{:?}", numbers);
}That *number is dereferencing. number is not the integer directly; it’s a mutable reference to the integer.
Consuming a vector
If we iterate without &, we move the values out of the vector:
fn main() {
let names = vec![String::from("Ana"), String::from("Luis")];
for name in names {
println!("{}", name);
}
// names can no longer be used here
}This is correct if we no longer need the vector afterwards. If we want to keep it, we iterate with &names.
Vectors and ownership
A Vec<T> owns its elements. When the vector goes out of scope, Rust frees all the values it contains.
fn main() {
let texts = vec![
String::from("one"),
String::from("two"),
];
// When main exits, the Vec and its Strings are freed
}This aligns with everything we saw about ownership. The vector is not a strange exception: it’s just another owner.