`Vec<T>`
EssentialPrerequisites
Vec<T> is Rust’s all-purpose growable sequence. Under the hood it is three fields: a pointer to a heap-allocated buffer, a length (how many elements are present), and a capacity (how many elements fit before reallocation). Most container work in Rust starts with a Vec.
Creating a Vec
Three common ways:
let mut v: Vec<i32> = Vec::new(); // empty, capacity 0
let mut v = Vec::with_capacity(8); // pre-allocate 8 slots, length 0
let v = vec![1, 2, 3, 4, 5]; // macro: initialise from a literal list
vec![] is syntactic sugar that expands to a Vec::new() followed by a series of pushes.
push, pop, and indexing
let mut v = vec![10, 20, 30];
v.push(40); // appends; v is now [10, 20, 30, 40]
let last = v.pop(); // removes and returns the last element: Some(40)
println!("{}", v[0]); // index by position: 10
println!("{}", v.len()); // 3
Indexing with [] panics if the index is out of bounds. Use .get(i) to receive an Option<&T> instead:
match v.get(10) {
Some(x) => println!("{x}"),
None => println!("out of bounds"),
}
Capacity and growth
When you push past the current capacity, Vec allocates a larger buffer — typically doubling — copies the elements, and frees the old buffer. The amortised cost per push is O(1).
let mut v: Vec<i32> = Vec::new();
println!("{}", v.capacity()); // 0
v.push(1);
println!("{}", v.capacity()); // typically 4; exact value is unspecified
If you know the final size in advance, Vec::with_capacity(n) avoids repeated reallocation.
Slice views
Vec<T> implements Deref<Target = [T]>, so wherever a slice &[T] is expected, a reference to a Vec<T> coerces automatically:
fn sum(values: &[i32]) -> i32 {
values.iter().sum()
}
let v = vec![1, 2, 3];
println!("{}", sum(&v)); // &Vec<i32> coerces to &[i32]
You can also take a subslice with a range index — no copying occurs:
let middle: &[i32] = &v[1..3]; // [2, 3]
let whole: &[i32] = &v[..]; // [1, 2, 3]
Iteration
A Vec is iterable in all three ownership flavors:
let mut v = vec![1, 2, 3];
for x in &v { /* x: &i32 — v still usable after */ }
for x in &mut v { /* x: &mut i32 — modify elements in place */ }
for x in v { /* x: i32 — v is consumed */ }
Memory and Drop
Vec implements Drop. When a Vec goes out of scope, it drops every element in the buffer and then frees the heap allocation — no manual free required.
{
let v = vec![String::from("a"), String::from("b")];
} // each String is dropped first, then the buffer is freed
Summary
- A
Vec<T>is a heap buffer with a length and a capacity. pushappends;popremoves the last element asOption<T>;[i]indexes (panics on bounds);.get(i)returnsOption<&T>.- Capacity grows automatically (typically doubles) when
pushexceeds it;with_capacityavoids reallocation when the size is known. &Vec<T>coerces to&[T], soVecworks everywhere a slice is accepted.- When a
Vecis dropped, all elements are dropped and the heap buffer is freed.