RustVectors (Vec)

Vectors in Rust

A Vec (pronounced "vector") is Rust's general-purpose, heap-allocated, growable array. It stores elements of the same type contiguously in memory, making it cache-friendly and fast. It is by far the most commonly used collection in Rust.

Creating a Vector

There are three common ways to create a vector.

RUST
// 1. Empty vector — type must be known via annotation or a later push
let mut v: Vec<i32> = Vec::new();

// 2. vec! macro — most concise, infers the type from the values
let v = vec![1, 2, 3, 4, 5];

// 3. Pre-allocated capacity — avoids reallocations when size is known
let mut v: Vec<String> = Vec::with_capacity(100);
println!("len={}, cap={}", v.len(), v.capacity()); // len=0, cap=100
Tip
Use Vec::with_capacity(n) when you know roughly how many items you will push. It allocates memory once up front instead of repeatedly doubling the internal buffer.
Adding Elements

RUST
let mut v = vec![1, 2, 3];

// Push a single element to the end — O(1) amortised
v.push(4);                        // [1, 2, 3, 4]

// Extend with any iterable
v.extend([5, 6, 7]);              // [1, 2, 3, 4, 5, 6, 7]

// append drains another Vec into this one (the other becomes empty)
let mut other = vec![8, 9];
v.append(&mut other);             // v = [1..9], other = []
Reading Elements

Rust gives you two ways to read an element. The choice determines how the program handles an out-of-bounds index.

RUST
let v = vec![10, 20, 30];

// Index operator — panics at runtime if the index is out of bounds
let x = v[1];
println!("{}", x); // 20

// .get() — returns Option<&T>, safe for untrusted indices
match v.get(10) {
    Some(val) => println!("Got {}", val),
    None      => println!("Index out of range"),
}
Warning
Using v[i] on an index that does not exist causes a panic and crashes your program. Prefer .get(i) whenever the index comes from user input or external data.
Iterating

There are three iteration patterns. The one you choose determines ownership of the vector.

RUST
let mut v = vec![1, 2, 3];

// Immutable borrow — v is still usable after the loop
for x in &v {
    println!("{}", x);
}

// Mutable borrow — modify elements in place
for x in &mut v {
    *x *= 2;
}
println!("{:?}", v); // [2, 4, 6]

// Consuming iteration — v is moved into the loop and dropped afterwards
for x in v {
    println!("{}", x);
}
// v cannot be used here
Modifying Elements

RUST
let mut v = vec![3, 1, 4, 1, 5, 9, 2, 6];

// Remove and return the last element
if let Some(last) = v.pop() {
    println!("Popped: {}", last); // 6
}

// Remove element at index (shifts elements left) — O(n)
let removed = v.remove(2);        // removes the element at index 2

// Insert at index (shifts elements right) — O(n)
v.insert(0, 99);

// Keep only elements satisfying a predicate
v.retain(|&x| x > 2);

// Remove consecutive duplicates (sort first if you want ALL duplicates removed)
let mut d = vec![1, 1, 2, 3, 3, 3, 4];
d.dedup();
println!("{:?}", d); // [1, 2, 3, 4]
Sorting

RUST
let mut nums = vec![5, 2, 8, 1, 9];

// Ascending sort — requires Ord
nums.sort();
println!("{:?}", nums); // [1, 2, 5, 8, 9]

// Custom comparator — descending
nums.sort_by(|a, b| b.cmp(a));
println!("{:?}", nums); // [9, 8, 5, 2, 1]

// Sort structs by a key field
#[derive(Debug)]
struct Person { name: String, age: u32 }

let mut people = vec![
    Person { name: "Alice".into(), age: 30 },
    Person { name: "Bob".into(),   age: 25 },
];
people.sort_by_key(|p| p.age);
println!("{:?}", people); // Bob (25) before Alice (30)

// f64 does not implement Ord — use partial_cmp
let mut floats = vec![3.1_f64, 1.5, 2.7];
floats.sort_by(|a, b| a.partial_cmp(b).unwrap());
Searching

RUST
let v = vec![10, 20, 30, 40, 50];

// Check membership — O(n) linear scan
println!("{}", v.contains(&30)); // true

// Find the first matching index — O(n)
let pos = v.iter().position(|&x| x == 30);
println!("{:?}", pos); // Some(2)

// Binary search on a sorted vector — O(log n)
match v.binary_search(&30) {
    Ok(idx)  => println!("Found at index {}", idx),  // 2
    Err(idx) => println!("Would insert at {}", idx),
}
Transforming with Iterators

Vectors integrate seamlessly with Rust's iterator adapter chain. The chain is lazy — no work happens until you call a consuming adapter like .collect().

RUST
let v = vec![1, 2, 3, 4, 5, 6];

// Map every element into a new Vec
let doubled: Vec<i32> = v.iter().map(|&x| x * 2).collect();
println!("{:?}", doubled); // [2, 4, 6, 8, 10, 12]

// Filter elements
let evens: Vec<&i32> = v.iter().filter(|&&x| x % 2 == 0).collect();
println!("{:?}", evens); // [2, 4, 6]

// Chain adapters — filter then map
let result: Vec<i32> = v.iter()
    .filter(|&&x| x % 2 == 0)
    .map(|&x| x * 10)
    .collect();
println!("{:?}", result); // [20, 40, 60]

// Enumerate — pairs each element with its index
for (i, val) in v.iter().enumerate() {
    println!("v[{}] = {}", i, val);
}

// Aggregate operations
let sum: i32   = v.iter().sum();
let max        = v.iter().max();
let product: i32 = v.iter().product();
println!("sum={}, max={:?}, product={}", sum, max, product);
Slicing

A slice &[T] is a view into a contiguous sequence. Vectors coerce to slices automatically, so any function accepting &[T] also accepts a &Vec<T>.

RUST
let v = vec![0, 1, 2, 3, 4, 5];

// Borrow a range — exclusive end
let mid: &[i32] = &v[1..3];
println!("{:?}", mid); // [1, 2]

// From index 2 to the end
let tail = &v[2..];
println!("{:?}", tail); // [2, 3, 4, 5]

// Functions that accept &[T] work with both Vec and arrays
fn sum_slice(s: &[i32]) -> i32 { s.iter().sum() }
println!("{}", sum_slice(&v)); // 15
Draining a Range

.drain() removes a range from a vector and returns an iterator over the removed elements. It is more efficient than repeated .remove() calls because it only shifts the remaining elements once.

RUST
let mut v = vec![1, 2, 3, 4, 5];

// Remove elements at indices 1 and 2, collect them
let drained: Vec<i32> = v.drain(1..3).collect();
println!("drained:   {:?}", drained); // [2, 3]
println!("remaining: {:?}", v);       // [1, 4, 5]

// Drain everything — empties the Vec but keeps its allocation
let mut v2 = vec![10, 20, 30];
for item in v2.drain(..) {
    println!("processing {}", item);
}
// v2 is now empty but its buffer is still allocated
Splitting and Chunking

RUST
let v = vec![1, 2, 3, 4, 5, 6];

// split_at — returns two non-overlapping slices
let (left, right) = v.split_at(3);
println!("{:?} | {:?}", left, right); // [1, 2, 3] | [4, 5, 6]

// chunks — non-overlapping windows of fixed size
for chunk in v.chunks(2) {
    println!("{:?}", chunk); // [1,2]  [3,4]  [5,6]
}

// windows — overlapping views of fixed size
for window in v.windows(3) {
    println!("{:?}", window); // [1,2,3]  [2,3,4]  [3,4,5]  [4,5,6]
}
Tip
.chunks() is great for processing data in batches (e.g., sending items 100 at a time). .windows() suits sliding-window algorithms like moving averages.
Storing Different Types with Enums

Vec requires all elements to share the same type. When you need heterogeneous elements, wrap them in an enum.

RUST
#[derive(Debug)]
enum Cell {
    Int(i64),
    Float(f64),
    Text(String),
}

let row: Vec<Cell> = vec![
    Cell::Int(42),
    Cell::Float(3.14),
    Cell::Text("hello".into()),
];

for cell in &row {
    match cell {
        Cell::Int(n)   => println!("int: {}", n),
        Cell::Float(f) => println!("float: {}", f),
        Cell::Text(s)  => println!("text: {}", s),
    }
}
Heterogeneous Collections with Trait Objects

When the set of types is open-ended (not known at compile time), use Vec<Box<dyn Trait>>.

RUST
trait Draw {
    fn draw(&self);
}

struct Circle    { radius: f64 }
struct Rectangle { width: f64, height: f64 }

impl Draw for Circle    { fn draw(&self) { println!("Circle r={}", self.radius); } }
impl Draw for Rectangle { fn draw(&self) { println!("Rect {}x{}", self.width, self.height); } }

let shapes: Vec<Box<dyn Draw>> = vec![
    Box::new(Circle    { radius: 5.0 }),
    Box::new(Rectangle { width: 3.0, height: 4.0 }),
];

for shape in &shapes {
    shape.draw();
}
Note
Trait objects carry a small overhead (heap allocation + vtable lookup) compared to the enum approach. Prefer enums when the set of variants is fixed; use trait objects when it is open-ended or driven by external plugins.
Capacity vs Length

A Vec tracks two sizes: length (elements currently stored) and capacity (elements it can hold before the next reallocation). When length reaches capacity, the Vec allocates a larger buffer — typically doubling — and moves everything over.

RUST
let mut v: Vec<i32> = Vec::new();
println!("len={} cap={}", v.len(), v.capacity()); // len=0 cap=0

v.push(1);
println!("len={} cap={}", v.len(), v.capacity()); // len=1 cap=4 (exact cap varies)

// Release excess capacity back to the allocator
v.shrink_to_fit();

// Reserve additional room without changing length
v.reserve(50);
println!("cap >= 51: {}", v.capacity() >= 51); // true

// Truncate — keep only the first n elements
let mut v = vec![1, 2, 3, 4, 5];
v.truncate(3);
println!("{:?}", v); // [1, 2, 3]

// Clear all elements, keeping the allocation for future pushes
v.clear();
println!("len={}", v.len()); // 0
Quick Reference

Operation

Method

Notes

Create empty

Vec::new()

Allocates nothing yet

Create with values

vec![1, 2, 3]

Most ergonomic

Pre-allocate

Vec::with_capacity(n)

Avoids reallocations

Add to end

.push(val)

O(1) amortised

Remove from end

.pop()

Returns Option<T>

Access safely

.get(i)

Returns Option<&T>

Access unsafely

v[i]

Panics on out-of-bounds

Remove at index

.remove(i)

O(n) — shifts elements

Insert at index

.insert(i, val)

O(n) — shifts elements

Keep matching

.retain(|x| pred)

In-place filter

Sort

.sort() / .sort_by()

Stable in-place sort

Find index

.iter().position()

O(n) linear search

Binary search

.binary_search(&val)

O(log n), vector must be sorted

Drain range

.drain(1..3)

Removes and yields elements

Slice view

&v[1..3]

Coerces to &[T]

Chunk iteration

.chunks(n)

Non-overlapping windows

Success
You now know how to create, read, modify, search, iterate, slice, and drain Rust vectors. Vec<T> covers the vast majority of dynamic-array needs in Rust programs.