RustArena

Vec in Rust: creating, adding, removing and iterating

A practical guide to Rust's Vec<T>: vec!, push and pop, get vs indexing, insert and remove, sort, retain, dedup, and with_capacity.

Vec<T> is Rust's growable array: a list of values of the same type, stored next to each other on the heap. It's the collection you'll use most.

Creating

let mut scores: Vec<u32> = Vec::new();  // empty, type given
let names = vec!["Ferris", "Corro"];     // with values, type inferred
let zeros = vec![0; 10];                 // ten zeros
let mut buf = Vec::with_capacity(1000);  // room for 1000 before reallocating

Reading

Adding and removing

Method Does Cost
push(x) add at the end fast (amortized O(1))
pop() remove the last, returns Option<T> fast
insert(i, x) add at index i, shifting the rest right O(n)
remove(i) take out index i, shifting the rest left O(n)
swap_remove(i) take out index i, filling the gap with the last fast, changes order
truncate(n), clear() drop elements past n / all of them
extend(other) append everything from an iterator
retain(|x| ...) keep only elements that pass a test O(n)
dedup() remove consecutive duplicates (sort first) O(n)

Sorting

sort() for types with a natural order (integers, strings). sort_by_key(|p| p.age) to sort by a field, sort_by(|a, b| b.score.cmp(&a.score)) for full control (this one sorts descending). f64 has no total order, so sort() on floats doesn't compile; use sort_by(|a, b| a.total_cmp(b)). sort_unstable* variants are a bit faster when equal elements don't need to keep their order.

Iterating

Capacity

A Vec has a length (elements in use) and a capacity (room reserved). When push exceeds the capacity, it allocates a bigger block (roughly double) and moves everything. That's why holding a reference into a Vec while pushing is forbidden (E0502), and why with_capacity helps when you know the size up front.

Common mistakes

fn main() {
    let v = vec![10, 20, 30];
    let i: i32 = 1;
    println!("{}", v[i]);
}
error[E0277]: the type `[{integer}]` cannot be indexed by `i32`

Convert with v[i as usize] (if i can't be negative) or use usize for the variable.

Example

#[derive(Debug)]
struct Item {
    name: &'static str,
    qty: u32,
}

fn main() {
    let mut inventory = vec![
        Item { name: "sword", qty: 1 },
        Item { name: "potion", qty: 5 },
        Item { name: "arrow", qty: 0 },
        Item { name: "shield", qty: 2 },
    ];
    inventory.push(Item { name: "map", qty: 1 });
    println!("{} kinds of item", inventory.len());

    inventory.retain(|it| it.qty > 0);
    inventory.sort_by_key(|it| it.name);
    let names: Vec<&str> = inventory.iter().map(|it| it.name).collect();
    println!("{names:?}");

    for it in &mut inventory {
        it.qty *= 2;
    }
    println!("potions: {:?}", inventory.iter().find(|it| it.name == "potion").map(|it| it.qty));

    println!("index 1: {:?}", inventory.get(1));
    println!("index 9: {:?}", inventory.get(9));
    let last = inventory.pop();
    println!("popped {last:?}, {} left", inventory.len());

    let mut readings: Vec<f64> = vec![3.5, -1.0, 2.25, 9.0];
    readings.sort_by(|a, b| a.total_cmp(b));
    println!("{readings:?}");

    let mut tags = vec!["b", "a", "b", "c", "a"];
    tags.sort();
    tags.dedup();
    println!("{tags:?}");
}

Output:

5 kinds of item
["map", "potion", "shield", "sword"]
potions: Some(10)
index 1: Some(Item { name: "potion", qty: 10 })
index 9: None
popped Some(Item { name: "sword", qty: 2 }), 3 left
[-1.0, 2.25, 3.5, 9.0]
["a", "b", "c"]

Practice it