Vectors (Vec)
Arrays have a fixed length, but most real lists grow and shrink: students joining a batch, orders arriving at a shop, readings from a sensor. For these, Rust gives you Vec<T>, usually called a vector: a list of values of one type that lives on the heap and can change size.
Vec is the collection you will use more than any other. In this lesson you will create vectors, read and change them safely, loop over them in the three ways Rust allows, and sort, filter and remove items.
Creating a vector and adding items
Create an empty vector with Vec::new() or a filled one with the vec! macro. push adds to the end and pop removes from the end:
fn main() {
let mut queue: Vec<String> = Vec::new();
queue.push(String::from("Aarav"));
queue.push(String::from("Isha"));
queue.push(String::from("Kabir"));
println!("{queue:?}, {} waiting", queue.len());
println!("First: {}", queue[0]);
println!("Tenth: {:?}", queue.get(9));
let last = queue.pop();
println!("Popped: {last:?}");
println!("Now: {queue:?}");
let marks = vec![56, 78, 91];
println!("{marks:?}");
}["Aarav", "Isha", "Kabir"], 3 waiting
First: Aarav
Tenth: None
Popped: Some("Kabir")
Now: ["Aarav", "Isha"]
[56, 78, 91]Vec<String>is the type: "a vector ofString". Withvec![56, 78, 91]Rust infersVec<i32>.queue[0]returns the item directly but panics if the index is out of range.queue.get(9)returns anOptioninstead.popreturns anOptiontoo, because the vector might be empty.- The vector owns its items. When
queueis dropped, everyStringinside it is dropped as well.
A vector stores its items next to each other on the heap. When it runs out of room it allocates a bigger block and moves the items there; this is why you cannot keep a reference to an item while pushing (the borrow checker stops you, as you saw in the Rust Introduction). If you know roughly how many items you will add, Vec::with_capacity(500) reserves space up front and avoids repeated moves.
Three ways to loop
How you loop decides whether you read, change or consume the vector:
fn main() {
let mut prices = vec![120, 80, 250];
for p in &prices { // read: p is &i32
print!("{p} ");
}
println!();
for p in &mut prices { // change: p is &mut i32
*p += *p / 10; // 10% price rise
}
println!("{prices:?}");
let mut total = 0;
for p in prices { // consume: p is i32, the vector is moved
total += p;
}
println!("Total: {total}");
// println!("{prices:?}"); // error: `prices` was moved by the loop
}120 80 250 [132, 88, 275] Total: 495
| Loop | Item type | Vector afterwards |
|---|---|---|
for x in &v | &T | Unchanged and usable |
for x in &mut v | &mut T | Changed and usable |
for x in v | T | Moved, no longer usable |
Most loops should borrow with &v. Consume the vector only when you really want to take its items out, for example to move them into another collection.
Changing a vector
Vec has a large toolbox of methods. These are the ones you will reach for most:
fn main() {
let mut rolls = vec![14, 3, 27, 3, 9, 21, 14];
rolls.sort();
println!("Sorted: {rolls:?}");
rolls.dedup();
println!("Unique: {rolls:?}");
rolls.insert(1, 5);
println!("Insert: {rolls:?}");
let removed = rolls.remove(0);
println!("Removed {removed}: {rolls:?}");
rolls.retain(|&r| r % 2 == 1);
println!("Odd: {rolls:?}");
println!("Has 21? {}", rolls.contains(&21));
rolls.extend([31, 33]);
println!("Extend: {rolls:?}, length {}", rolls.len());
}Sorted: [3, 3, 9, 14, 14, 21, 27] Unique: [3, 9, 14, 21, 27] Insert: [3, 5, 9, 14, 21, 27] Removed 3: [5, 9, 14, 21, 27] Odd: [5, 9, 21, 27] Has 21? true Extend: [5, 9, 21, 27, 31, 33], length 6
dedupremoves neighbouring duplicates only, so sort first.insert(i, x)andremove(i)shift every later item, which is slow for long vectors.swap_remove(i)is fast when order does not matter: it moves the last item into the gap.retainkeeps the items for which the small function|&r| r % 2 == 1returnstrue. That|...|syntax is a closure; see Closures.containstakes a reference, hence&21.
Vectors of structs and enums
A vector holds one type, but that type can be a struct, or an enum when you need different kinds of items in one list:
struct Player {
name: String,
runs: u32,
}
fn main() {
let mut team = vec![
Player { name: String::from("Rohan"), runs: 45 },
Player { name: String::from("Farhan"), runs: 88 },
Player { name: String::from("Ananya"), runs: 61 },
];
team.sort_by(|a, b| b.runs.cmp(&a.runs)); // highest runs first
for p in &team {
println!("{:<8}{:>4}", p.name, p.runs);
}
}Farhan 88 Ananya 61 Rohan 45
sort_by takes a closure that compares two items; b.runs.cmp(&a.runs) (with b first) sorts in descending order. sort_by_key(|p| p.runs) is shorter for ascending order. In the format string, {:<8} left-aligns the name in 8 characters and {:>4} right-aligns the runs in 4, which lines up the columns.
Floating-point numbers cannot use plain sort(), because f64 has the special value NaN that is not comparable to anything. Sort them with v.sort_by(|a, b| a.total_cmp(b)).
Common mistakes
- Indexing past the end.
v[10]on a 3-item vector panics. Usegetwhen the index comes from outside your control. - Pushing while holding a reference to an item. Copy the value out first (
let first = v[0];) or finish using the reference before you push. - Looping with
for x in vand then usingv. That loop moved the vector; loop over&vinstead. - Calling
dedupon unsorted data. It only removes duplicates that sit next to each other. - Removing from the front of a long vector in a loop. Each
remove(0)shifts everything. UseVecDequefromstd::collectionsfor queues.
Key points
Vec<T>is a growable list on the heap; create it withVec::new()orvec![...].pushandpopwork at the end;v[i]panics on a bad index whileget(i)returns anOption.- Loop with
&vto read,&mut vto change items andvto consume the vector. sort,dedup,retain,insert,remove,containsandextendcover most everyday changes.- Vectors can hold structs or enums; sort them with
sort_byorsort_by_key.
Exercise
A coaching centre in Kochi records test marks: vec![67, 32, 88, 45, 29, 91, 74]. Write a program that: adds two late marks (55 and 30), counts how many students failed (below 33), removes the failed marks with retain, sorts the rest from highest to lowest, and prints the top three.
Show solution
fn main() {
let mut marks = vec![67, 32, 88, 45, 29, 91, 74];
marks.push(55);
marks.push(30);
let mut failed = 0;
for m in &marks {
if *m < 33 {
failed += 1;
}
}
println!("Failed: {failed}");
marks.retain(|&m| m >= 33);
marks.sort_by(|a, b| b.cmp(a));
println!("Passed, highest first: {marks:?}");
println!("Top three: {:?}", &marks[..3]);
}Failed: 3 Passed, highest first: [91, 88, 74, 67, 55, 45] Top three: [91, 88, 74]
The marks 32, 29 and 30 are below 33. &marks[..3] is a slice of the first three items, so nothing is copied.
Quick Quiz
1. What does v.pop() return for an empty vector?
pop returns Option<T>, which is None when there is nothing to remove.
2. Which loop lets you change every item of v and still use v afterwards?
A mutable borrow gives &mut T items, so you can write *x += 1 and keep the vector.
3. What is vec![2, 2, 5, 2].dedup() going to leave?
dedup removes only consecutive duplicates; the last 2 is not next to another 2.
4. Why can't you call .sort() on a Vec<f64>?
sort requires the Ord trait, which floats do not implement; total_cmp gives a total order.
Learn Rust with a mentor
Free lessons get you started. Our Programming & Software Development training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
