BBuzzard

Vec<T> is a growable array on the heap: a pointer, a length, and a capacity. push and pop work at the end in O(1). Capacity is room already reserved; with_capacity and reserve avoid repeated reallocations when you know the size. Indexing v[i] panics on a bad index; get(i) returns an Option.

sort needs Ord and is stable (equal items keep their order). sort_by takes a comparator, sort_by_key a key function, and the _unstable variants are faster but may reorder equal items. binary_search on a sorted slice returns Ok(index) when found or Err(insert_position) when not.

Creating, pushing, popping, and capacity

fn demo_basics() {
    println!("-- basics --");

    let mut v = Vec::with_capacity(2);
    println!("empty len {} cap {}", v.len(), v.capacity());
    v.push(1);
    v.push(2);
    v.push(3); // exceeds capacity, so it reallocates
    println!("len {} cap>=3 {} {:?}", v.len(), v.capacity() >= 3, v);
    println!("pop {:?} first {:?} last {:?}", v.pop(), v.first(), v.last());

    let zeros = vec![0; 5];
    let from_iter: Vec<_> = (1..=3).collect();
    println!("{zeros:?} {from_iter:?}");
    v.shrink_to_fit();
    println!("after shrink cap {}", v.capacity());
}

Safe and unsafe-looking access

fn demo_access() {
    println!("-- access --");

    let mut v = vec![10, 20, 30];
    println!("v[1] {} get(9) {:?}", v[1], v.get(9));

    if let Some(x) = v.get_mut(0) {
        *x += 1;
    }

    println!("after get_mut {v:?}");
    let hook = std::panic::take_hook();
    std::panic::set_hook(Box::new(|_| {})); // hide the panic message
    let caught = std::panic::catch_unwind(|| vec![1][5]);
    std::panic::set_hook(hook);
    println!("out of range index panics: {}", caught.is_err());
    println!("contains 20? {} is_empty? {}", v.contains(&20), v.is_empty());
}

Inserting and removing in the middle

fn demo_insert_remove() {
    println!("-- insert / remove --");

    let mut v = vec!['a', 'b', 'd'];
    v.insert(2, 'c');
    println!("insert {v:?}");

    let removed = v.remove(0); // O(n): shifts everything left
    println!("remove {removed} -> {v:?}");

    let swapped = v.swap_remove(0); // O(1): moves the last item into the gap
    println!("swap_remove {swapped} -> {v:?}");
    v.truncate(1);
    println!("truncate {v:?}");
    v.clear();
    println!("clear {v:?}");
}

Bulk operations

fn demo_bulk() {
    println!("-- bulk --");

    let mut v = vec![1, 2, 3, 4, 5, 6];
    v.retain(|n| n % 2 == 0);
    println!("retain even {v:?}");
    v.extend([8, 10]);
    v.extend_from_slice(&[12]);
    println!("extend {v:?}");

    let drained: Vec<_> = v.drain(1..3).collect();
    println!("drained {drained:?} left {v:?}");

    let tail = v.split_off(2);
    println!("split_off {v:?} {tail:?}");

    let mut a = vec![1, 2];
    let mut b = vec![3, 4];
    a.append(&mut b);
    println!("append {a:?} b now {b:?}");

    let mut d = vec![1, 1, 2, 3, 3, 3, 1];
    d.dedup();
    println!("dedup consecutive {d:?}");

    let mut words = vec!["a", "b", "c"];
    words.reverse();
    println!("{} {:?}", words.concat(), words.iter().rev().collect::<Vec<_>>());
}

Sorting

fn demo_sorting() {
    println!("-- sorting --");

    let mut names = vec!["bob", "Cy", "ann"];
    names.sort();
    println!("byte order {names:?}");
    names.sort_by_key(|s| s.to_lowercase());
    println!("case-insensitive {names:?}");

    let mut pairs = vec![("ke", 80), ("joe", 20), ("amy", 80)];
    pairs.sort_by_key(|p| Reverse(p.1));
    println!("by score desc, stable {pairs:?}");
    pairs.sort_by(|a, b| b.1.cmp(&a.1).then(a.0.cmp(b.0)));
    println!("score desc then name {pairs:?}");

    let mut nums = vec![5, 3, 9, 1, 7];
    nums.sort_unstable();
    println!("unstable {nums:?} is_sorted {}", nums.is_sorted());

    let mut floats = vec![2.5, -1.0, 0.0];
    floats.sort_by(f64::total_cmp);
    println!("floats {floats:?}");

    // Partial sort: the 3 smallest, without sorting the rest.
    let mut big = vec![9, 4, 7, 1, 8, 2, 6];
    let (below, third, _) = big.select_nth_unstable(2);
    let mut smallest = below.to_vec();
    smallest.push(*third);
    smallest.sort();
    println!("three smallest {smallest:?}");
}

Binary search on sorted data

fn demo_binary_search() {
    println!("-- binary search --");

    let v = [1, 3, 5, 7];
    println!("{:?} {:?}", v.binary_search(&5), v.binary_search(&4));

    // Insert while keeping the Vec sorted.
    let mut sorted = vec![10, 20, 40];
    let pos = sorted.binary_search(&30).unwrap_or_else(|i| i);
    sorted.insert(pos, 30);
    println!("inserted at {pos}: {sorted:?}");

    let people = [("ann", 30), ("ben", 35), ("cy", 41)];
    println!("by key {:?}", people.binary_search_by_key(&35, |p| p.1));
    println!("partition_point first >= 33: {}", people.partition_point(|p| p.1 < 33));

    // Searching unsorted data gives a meaningless answer, not an error.
}

Vec of structs, and Vec<Vec<T>>

fn demo_nested() {
    println!("-- nested --");

    let mut grid = vec![vec![0; 3]; 2];
    grid[1][2] = 7;
    println!("{grid:?}");
    #[derive(Debug)]
    struct Task {
        id: u32,
        done: bool,
    }

    let mut tasks: Vec<Task> =
        (1..=4).map(|id| Task { id, done: id % 2 == 0 }).collect();
    tasks.retain(|t| !t.done);
    println!("open tasks {:?}", tasks.iter().map(|t| t.id).collect::<Vec<_>>());
    println!("{:?}", tasks.first());
}
$ cargo run --bin 36_vec
-- basics --
empty len 0 cap 2
len 3 cap>=3 true [1, 2, 3]
pop Some(3) first Some(1) last Some(2)
[0, 0, 0, 0, 0] [1, 2, 3]
after shrink cap 2
-- access --
v[1] 20 get(9) None
after get_mut [11, 20, 30]
out of range index panics: true
contains 20? true is_empty? false
-- insert / remove --
insert ['a', 'b', 'c', 'd']
remove a -> ['b', 'c', 'd']
swap_remove b -> ['d', 'c']
truncate ['d']
clear []
-- bulk --
retain even [2, 4, 6]
extend [2, 4, 6, 8, 10, 12]
drained [4, 6] left [2, 8, 10, 12]
split_off [2, 8] [10, 12]
append [1, 2, 3, 4] b now []
dedup consecutive [1, 2, 3, 1]
cba ["a", "b", "c"]
-- sorting --
byte order ["Cy", "ann", "bob"]
case-insensitive ["ann", "bob", "Cy"]
by score desc, stable [("ke", 80), ("amy", 80), ("joe", 20)]
score desc then name [("amy", 80), ("ke", 80), ("joe", 20)]
unstable [1, 3, 5, 7, 9] is_sorted true
floats [-1.0, 0.0, 2.5]
three smallest [1, 2, 4]
-- binary search --
Ok(2) Err(2)
inserted at 2: [10, 20, 30, 40]
by key Ok(1)
partition_point first >= 33: 1
-- nested --
[[0, 0, 0], [0, 0, 7]]
open tasks [1, 3]
Some(Task { id: 1, done: false })

Full program

36_vec/main.rs · 187 lines
View on GitHub
// 36. Vec, sorting, and binary search
//
// Vec<T> is a growable array on the heap: a pointer, a length, and a capacity. push and
// pop work at the end in O(1). Capacity is room already reserved; with_capacity and
// reserve avoid repeated reallocations when you know the size. Indexing v[i] panics on a
// bad index; get(i) returns an Option.
//
// sort needs Ord and is stable (equal items keep their order). sort_by takes a
// comparator, sort_by_key a key function, and the _unstable variants are faster but may
// reorder equal items. binary_search on a sorted slice returns Ok(index) when found or
// Err(insert_position) when not.
//
// Run: cargo run --bin 36_vec

use std::cmp::Reverse;

// Example 1: creating, pushing, popping, and capacity.
fn demo_basics() {
    println!("-- basics --");

    let mut v = Vec::with_capacity(2);
    println!("empty len {} cap {}", v.len(), v.capacity());
    v.push(1);
    v.push(2);
    v.push(3); // exceeds capacity, so it reallocates
    println!("len {} cap>=3 {} {:?}", v.len(), v.capacity() >= 3, v);
    println!("pop {:?} first {:?} last {:?}", v.pop(), v.first(), v.last());

    let zeros = vec![0; 5];
    let from_iter: Vec<_> = (1..=3).collect();
    println!("{zeros:?} {from_iter:?}");
    v.shrink_to_fit();
    println!("after shrink cap {}", v.capacity());
}

// Example 2: safe and unsafe-looking access.
fn demo_access() {
    println!("-- access --");

    let mut v = vec![10, 20, 30];
    println!("v[1] {} get(9) {:?}", v[1], v.get(9));

    if let Some(x) = v.get_mut(0) {
        *x += 1;
    }

    println!("after get_mut {v:?}");
    let hook = std::panic::take_hook();
    std::panic::set_hook(Box::new(|_| {})); // hide the panic message
    let caught = std::panic::catch_unwind(|| vec![1][5]);
    std::panic::set_hook(hook);
    println!("out of range index panics: {}", caught.is_err());
    println!("contains 20? {} is_empty? {}", v.contains(&20), v.is_empty());
}

// Example 3: inserting and removing in the middle.
fn demo_insert_remove() {
    println!("-- insert / remove --");

    let mut v = vec!['a', 'b', 'd'];
    v.insert(2, 'c');
    println!("insert {v:?}");

    let removed = v.remove(0); // O(n): shifts everything left
    println!("remove {removed} -> {v:?}");

    let swapped = v.swap_remove(0); // O(1): moves the last item into the gap
    println!("swap_remove {swapped} -> {v:?}");
    v.truncate(1);
    println!("truncate {v:?}");
    v.clear();
    println!("clear {v:?}");
}

// Example 4: bulk operations.
fn demo_bulk() {
    println!("-- bulk --");

    let mut v = vec![1, 2, 3, 4, 5, 6];
    v.retain(|n| n % 2 == 0);
    println!("retain even {v:?}");
    v.extend([8, 10]);
    v.extend_from_slice(&[12]);
    println!("extend {v:?}");

    let drained: Vec<_> = v.drain(1..3).collect();
    println!("drained {drained:?} left {v:?}");

    let tail = v.split_off(2);
    println!("split_off {v:?} {tail:?}");

    let mut a = vec![1, 2];
    let mut b = vec![3, 4];
    a.append(&mut b);
    println!("append {a:?} b now {b:?}");

    let mut d = vec![1, 1, 2, 3, 3, 3, 1];
    d.dedup();
    println!("dedup consecutive {d:?}");

    let mut words = vec!["a", "b", "c"];
    words.reverse();
    println!("{} {:?}", words.concat(), words.iter().rev().collect::<Vec<_>>());
}

// Example 5: sorting.
fn demo_sorting() {
    println!("-- sorting --");

    let mut names = vec!["bob", "Cy", "ann"];
    names.sort();
    println!("byte order {names:?}");
    names.sort_by_key(|s| s.to_lowercase());
    println!("case-insensitive {names:?}");

    let mut pairs = vec![("ke", 80), ("joe", 20), ("amy", 80)];
    pairs.sort_by_key(|p| Reverse(p.1));
    println!("by score desc, stable {pairs:?}");
    pairs.sort_by(|a, b| b.1.cmp(&a.1).then(a.0.cmp(b.0)));
    println!("score desc then name {pairs:?}");

    let mut nums = vec![5, 3, 9, 1, 7];
    nums.sort_unstable();
    println!("unstable {nums:?} is_sorted {}", nums.is_sorted());

    let mut floats = vec![2.5, -1.0, 0.0];
    floats.sort_by(f64::total_cmp);
    println!("floats {floats:?}");

    // Partial sort: the 3 smallest, without sorting the rest.
    let mut big = vec![9, 4, 7, 1, 8, 2, 6];
    let (below, third, _) = big.select_nth_unstable(2);
    let mut smallest = below.to_vec();
    smallest.push(*third);
    smallest.sort();
    println!("three smallest {smallest:?}");
}

// Example 6: binary search on sorted data.
fn demo_binary_search() {
    println!("-- binary search --");

    let v = [1, 3, 5, 7];
    println!("{:?} {:?}", v.binary_search(&5), v.binary_search(&4));

    // Insert while keeping the Vec sorted.
    let mut sorted = vec![10, 20, 40];
    let pos = sorted.binary_search(&30).unwrap_or_else(|i| i);
    sorted.insert(pos, 30);
    println!("inserted at {pos}: {sorted:?}");

    let people = [("ann", 30), ("ben", 35), ("cy", 41)];
    println!("by key {:?}", people.binary_search_by_key(&35, |p| p.1));
    println!("partition_point first >= 33: {}", people.partition_point(|p| p.1 < 33));

    // Searching unsorted data gives a meaningless answer, not an error.
}

// Example 7: Vec of structs, and Vec<Vec<T>>.
fn demo_nested() {
    println!("-- nested --");

    let mut grid = vec![vec![0; 3]; 2];
    grid[1][2] = 7;
    println!("{grid:?}");
    #[derive(Debug)]
    struct Task {
        id: u32,
        done: bool,
    }

    let mut tasks: Vec<Task> =
        (1..=4).map(|id| Task { id, done: id % 2 == 0 }).collect();
    tasks.retain(|t| !t.done);
    println!("open tasks {:?}", tasks.iter().map(|t| t.id).collect::<Vec<_>>());
    println!("{:?}", tasks.first());
}

fn main() {
    demo_basics();
    demo_access();
    demo_insert_remove();
    demo_bulk();
    demo_sorting();
    demo_binary_search();
    demo_nested();
}