Skip to content
AgileLogics
6 min read

Vectors

Prerequisites: Tuples & arrays

A vector, Vec<T>, is a growable list of values of the same type T, stored on the heap. It's the collection you'll reach for most often.

Syntax

let mut names: Vec<String> = Vec::new(); // empty; type must be known
let mut numbers = vec![1, 2, 3];        // `vec!` macro with values

numbers.push(4);                        // add to the end
let third = numbers[2];                 // index: panics if out of range
let maybe = numbers.get(10);            // `get`: returns Some(&value) or None
numbers.len();                          // number of items
  • Vec::new() creates an empty vector. Rust needs to know the element type, either from an annotation or from what you push later.
  • vec![...] creates a vector with initial values.
  • push adds to the end; pop removes the last item; insert(i, x) and remove(i) work at a position.
  • Indexing with v[i] panics if i is out of range.
  • get(i) returns an Option: Some(&value) if the index exists, None if it doesn't. It's the safe choice when the index might be invalid. Handle it with match (the Enums & match lesson covers Option properly).
  • Loop with for x in &v to read each item, or for x in &mut v to change each one (write *x to reach the value behind the reference). for x in v without & moves the vector into the loop.

Example

fn main() {
    let mut numbers = vec![10, 20, 30];
    numbers.push(40);
    println!("{numbers:?} has {} items", numbers.len());

    println!("numbers[1] = {}", numbers[1]);
    match numbers.get(10) {
        Some(n) => println!("found {n}"),
        None => println!("no item at index 10"),
    }

    for n in &mut numbers {
        *n *= 2;
    }
    for n in &numbers {
        print!("[{n}]");
    }
    println!();

    numbers.remove(0);
    numbers.insert(0, 5);
    println!("{numbers:?}");
    println!("contains 60? {}", numbers.contains(&60));

    let mut words: Vec<String> = Vec::new();
    words.push(String::from("pear"));
    words.push(String::from("apple"));
    words.push(String::from("fig"));
    words.sort();
    println!("{words:?}");

    if let Some(last) = words.pop() {
        println!("popped {last}, {} left", words.len());
    }
}

if let is a shorter form of match for when you only care about one case; it's explained in the Enums & match lesson.

Expected output

[10, 20, 30, 40] has 4 items
numbers[1] = 20
no item at index 10
[20][40][60][80]
[5, 40, 60, 80]
contains 60? true
["apple", "fig", "pear"]
popped pear, 2 left

Common mistakes

  • Holding a reference while changing the vector:
let mut v = vec![1, 2, 3];
let first = &v[0];
v.push(4);
println!("{first}");
error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable

This looks harmless, but push may move the whole vector to a bigger block of memory, which would leave first pointing at freed memory. Rust refuses to compile it.

  • Indexing out of range at runtime panics with index out of bounds: the len is 3 but the index is 10. Use .get() when the index might not exist.
  • Writing for n in numbers and then trying to use numbers again. Without &, the loop takes ownership of the vector.

Practice

Create an empty Vec<i32>, push every even number from 1 to 10 into it with a loop, then print the vector and the sum of its items:

[2, 4, 6, 8, 10]
Sum: 30
Show solution
fn main() {
    let mut evens: Vec<i32> = Vec::new();
    for n in 1..=10 {
        if n % 2 == 0 {
            evens.push(n);
        }
    }

    let mut sum = 0;
    for n in &evens {
        sum += n;
    }

    println!("{evens:?}");
    println!("Sum: {sum}");
}