Rust Vectors Explained: Vec, push, get, Loops and the Borrow Trap
November 10, 2026
Welcome to episode 15 of Rust for Beginners, a series for complete beginners (no programming experience needed). In the last video, we learned error handling. Today, we'll learn about vectors: lists that can grow and shrink. Every code block below is the exact code from the video, ready to copy.
- All episodes are in the Rust Tutorial for Beginners playlist.
- Versions used: Rust 1.99.1 on Ubuntu, VS Code with rust-analyzer.
1. New project
Let's create a new project called vectors, and open it in VS Code.
cargo new vectors
cd vectors2. Vec::new
An array has a fixed length. A vector, Vec, can grow. We create an empty one with Vec colon colon new. It must be mutable to add items.
src/main.rs:
fn main() {
let mut numbers: Vec<i32> = Vec::new();
numbers.push(10);
numbers.push(20);
numbers.push(30);
println!("{numbers:?}, length {}", numbers.len());
let fruits = vec!["apple", "banana"];
println!("{fruits:?}");
}Output: [10, 20, 30], length 3 / ["apple", "banana"]
3. push
push adds an item to the end, and len gives the length.
4. vec!
With the vec macro, we can create a vector with items already inside.
5. Run A
Let's run it.
cargo run6. Access
There are two ways to read an item. Square brackets give the item directly. get gives an Option: Some if the index exists, None if it doesn't.
fn main() {
let numbers = vec![10, 20, 30];
println!("First: {}", numbers[0]);
println!("get(1): {:?}", numbers.get(1));
println!("get(10): {:?}", numbers.get(10));
println!("{}", numbers[10]);
}Output (verified in the VM; the thread number changes every run):
First: 10
get(1): Some(20)
get(10): None
thread 'main' (3746) panicked at src/main.rs:8:27:
index out of bounds: the len is 3 but the index is 10
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace7. Run B
Let's run it. get ten gives None, safely. But square brackets with index ten panic: index out of bounds.
cargo run8. Which
So use square brackets when you know the index is valid, and get when it might not be.
9. Loop
To go through a vector, we loop over a reference to it, so the vector isn't moved.
fn main() {
let mut numbers = vec![30, 10, 20];
for n in &numbers {
println!("n = {n}");
}
for n in &mut numbers {
*n *= 2;
}
println!("Doubled: {numbers:?}");
numbers.sort();
println!("Sorted: {numbers:?}");
let last = numbers.pop();
println!("Popped {last:?}, left {numbers:?}");
println!("Contains 20? {}", numbers.contains(&20));
}Output:
n = 30
n = 10
n = 20
Doubled: [60, 20, 40]
Sorted: [20, 40, 60]
Popped Some(60), left [20, 40]
Contains 20? true10. Change
To change every item, we loop over ampersand mut, and use a star to reach the value behind each reference.
11. Methods
Vectors have many helpful methods: sort, pop to remove the last item, and contains.
12. Run C
Let's run it. Doubled, sorted, and pop gives Some sixty, because the vector could have been empty.
cargo run13. Borrow trap
One last trap. We hold a reference to the first item, and then push a new one.
cargo runfn main() {
let mut numbers = vec![1, 2, 3];
let first = &numbers[0];
numbers.push(4);
println!("First: {first}");
}Error:
error[E0502]: cannot borrow `numbers` as mutable because it is also borrowed as immutable
--> src/main.rs:4:5
|
3 | let first = &numbers[0];
| ------- immutable borrow occurs here
4 | numbers.push(4);
| ^^^^^^^^^^^^^^^ mutable borrow occurs here
5 | println!("First: {first}");
| ----- immutable borrow later used here14. Why
Rust stops us. When a vector grows, it may move to a bigger place in memory, and first would point to the old place. The borrow rules protect us again.
Recap
To recap: Vec is a growable list. Create it with Vec new or the vec macro, add with push, read with brackets or get, and loop over a reference.