TL;DR:
Vec<T>is Rust's growable, heap-allocated array, the equivalent ofstd::vectorin C++ orArrayListin Java. It stores elements contiguously in memory, grows by doubling capacity when full, and is the most commonly used collection in Rust.Vec<T>owns its data, when it goes out of scope, the memory is freed. A&[T](slice) is a borrowed view into aVecor any contiguous sequence, and is what you use in function parameters when you only need to read.
What Is Vec<T> in Rust?
Vec<T> is a heap-allocated, growable array that owns its elements ; Rust's go-to collection for ordered sequences of values.
fn main() {
// Create
let mut v: Vec<i32> = Vec::new();
let v2 = vec![1, 2, 3, 4, 5]; // macro shorthand
// Push and pop
v.push(10);
v.push(20);
v.push(30);
let last = v.pop(); // Some(30)
// Access; panics on out-of-bounds
println!("{}", v[0]); // 10
// Safe access; returns Option
if let Some(x) = v.get(1) {
println!("{x}"); // 20
}
println!("Length: {}", v.len());
println!("Capacity: {}", v.capacity()); // may be > len
}How Does Vec Manage Memory?
Vec maintains three values: a pointer to heap memory, the current length, and the allocated capacity. When length reaches capacity, it reallocates with double the space.
fn main() {
let mut v: Vec<i32> = Vec::new();
println!("len={}, cap={}", v.len(), v.capacity()); // 0, 0
v.push(1);
println!("len={}, cap={}", v.len(), v.capacity()); // 1, 4 (initial alloc)
// Pre-allocate when you know the size; avoids repeated reallocations
let mut v = Vec::with_capacity(1000);
for i in 0..1000 {
v.push(i); // no reallocations
}
// Shrink excess capacity
v.shrink_to_fit();
// Release memory explicitly
drop(v); // or let it go out of scope
}Each reallocation copies all elements, with_capacity avoids this when the final size is known.
How Do You Iterate Over a Vec?
Use for loops with .iter() (borrow), .iter_mut() (mutable borrow), or .into_iter() (consume), or chain iterator adapters.
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
// Immutable iteration
for n in &numbers {
print!("{n} ");
}
// Mutable iteration
let mut values = vec![1, 2, 3];
for v in &mut values {
*v *= 2; // dereference to modify
}
println!("{:?}", values); // [2, 4, 6]
// Iterator adapters
let doubled: Vec<i32> = numbers.iter().map(|&x| x * 2).collect();
let evens: Vec<&i32> = numbers.iter().filter(|&&x| x % 2 == 0).collect();
let sum: i32 = numbers.iter().sum();
println!("doubled: {:?}", doubled);
println!("evens: {:?}", evens);
println!("sum: {sum}");
}What Is the Difference Between Vec<T> and &[T]?
Vec<T> owns its data. &[T] (a slice) is a borrowed view into any contiguous sequence, a Vec, an array, or part of either. Use &[T] in function parameters for maximum flexibility.
// Accepts Vec, arrays, and slices; most flexible
fn sum(numbers: &[i32]) -> i32 {
numbers.iter().sum()
}
fn main() {
let v = vec![1, 2, 3, 4, 5];
let arr = [1, 2, 3, 4, 5];
println!("{}", sum(&v)); // Vec coerces to &[i32]
println!("{}", sum(&arr)); // array coerces to &[i32]
println!("{}", sum(&v[1..3])); // slice of Vec
}&[T] is two words: a pointer and a length. It has no capacity and cannot grow. It is the idiomatic way to pass sequences to functions you don't need to own or modify.
Common Vec Operations
fn main() {
let mut v = vec![3, 1, 4, 1, 5, 9, 2, 6];
// Sort
v.sort(); // [1, 1, 2, 3, 4, 5, 6, 9]
v.sort_by(|a, b| b.cmp(a)); // descending
v.sort_by_key(|&x| x % 3); // by remainder
// Dedup (removes consecutive duplicates; sort first)
v.sort();
v.dedup(); // [1, 2, 3, 4, 5, 6, 9]
// Retain only elements matching predicate
v.retain(|&x| x % 2 == 0); // [2, 4, 6]
// Extend from another iterator
v.extend([8, 10, 12]);
// Drain; remove a range and iterate over removed items
let removed: Vec<i32> = v.drain(0..2).collect();
// Truncate to length
v.truncate(3);
// Clear all elements (keeps allocation)
v.clear();
// Concatenate vecs
let a = vec![1, 2];
let mut b = vec![3, 4];
b.extend(a.iter()); // [3, 4, 1, 2]
}Frequently Asked Questions
Use arrays when the size is fixed and known at compile time, they live on the stack and have no allocation overhead. Use Vec when the size varies at runtime or is not known at compile time. Arrays are slightly faster for small, fixed data; Vec is more flexible.
.remove(i) removes the element at index i and shifts all subsequent elements left ; O(n). .swap_remove(i) swaps the element with the last element and pops ; O(1) but changes order. Choose based on whether order matters.
let strings = vec!["hello".to_string(), "world".to_string()];
let strs: Vec<&str> = strings.iter().map(String::as_str).collect();A heterogeneous collection, a Vec of owned trait objects. Use this when you need to store different concrete types that all implement the same trait in one collection. Each element is heap-allocated and accessed via dynamic dispatch.
Sources
Related Glossary Terms
- Ownership:
Vecowns its elements; passing it moves ownership - Iterator:
Vecis the most common iterator source - Generic:
Vec<T>is generic over element typeT - String:
Stringis internally aVec<u8> - HashMap:
VecandHashMapare the two foundational Rust collections to compare early - Rand: Random sampling and shuffling frequently operate on
Vec<T> - Slice: Borrowed slices are the zero-copy view into Vec data

