Vec
In Rust programs we use vectors to store values in an efficient way. Programs often create vectors in many places, and many times.
With many syntax forms, we can create vectors in an elegant and concise way in Rust programs. We can repeat values, and even use a capacity to speed up later push()
calls.
This Rust program initializes vectors in 7 different ways. We see the vec
macro, which can be empty, have element values, or a repeated element value.
with_capacity
—this will provide memory for the specified number of elements to be added with no resizing.extend_from_slice
to append many elements from a slice at once (another vector can be appended as well).from()
function to copy its initial values.fn main() { // Empty vector. let mut values = vec![]; values.push(5); println!("{:?}", values); // Vector with 3 values. let values = vec![10, 20, 30]; println!("{:?}", values); // Vector with repeated value. let values = vec![1; 2]; println!("{:?}", values); // Vector with capacity. let mut values = Vec::with_capacity(100); values.push(3); println!("{:?}", values); // Empty vector, extend from slice. let mut values = Vec::new(); values.extend_from_slice(&[1, 2, 3]); println!("{:?}", values); // Vector from. let values = Vec::from([4, 5]); println!("{:?}", values); }[5] [10, 20, 30] [1, 1] [3] [1, 2, 3] [4, 5]
The push()
function adds an element onto the end of the vector. It is important that our vector be a mutable (mut
) variable for calling push.
Remove()
removes by index. So a call to remove "1" will remove the element at index 1—the second element.fn main() { let mut values = vec![1, 2]; println!("{:?}", values); // Add value to end. values.push(3); println!("{:?}", values); // Remove value with index 1. values.remove(1); println!("{:?}", values); }[1, 2] [1, 2, 3] [1, 3]
Sometimes we need to initialize vectors in a loop or assign elements by index directly. We must make sure to use the mut
keyword to have a mutable vector.
for
-loops on the vector. We must make sure to borrow the vector to allow it to be used again later.fn main() { // Create all-zero vector, then assign elements into vector. let mut items = vec![0; 10]; items[0] = -2; items[5] = 10; items[9] = -1; // Print with for-loop. for item in &items { println!("ITEM: {}", item); } // Modify vector by index. for i in 0..items.len() { items[i] = items[i] * 2; } // Print with loop again. for item in &items { println!("MULTIPLIED: {}", item); } }ITEM: -2 ITEM: 0 ITEM: 0 ITEM: 0 ITEM: 0 ITEM: 10 ITEM: 0 ITEM: 0 ITEM: 0 ITEM: -1 MULTIPLIED: -4 MULTIPLIED: 0 MULTIPLIED: 0 MULTIPLIED: 0 MULTIPLIED: 0 MULTIPLIED: 20 MULTIPLIED: 0 MULTIPLIED: 0 MULTIPLIED: 0 MULTIPLIED: -2
Stack
There is no special stack type in Rust—the Vec
doubles as a stack. We can use a while-let
loop to pop each element until no more elements are present.
Pop()
takes the last element in the Vec
, removes it, and returns it. If the Vec
is empty, None
is returned.fn main() { let mut stack = vec![]; stack.push(1); stack.push(2); // Use Vec as a Stack. while let Some(item) = stack.pop() { println!("STACK ITEM: {}", item); } }STACK ITEM: 2 STACK ITEM: 1
Suppose we want to place the logic for vector initialization in a separate function. We can do return a Vec
from a function.
get_new_vector()
function returns a vector of 2 strings. We can use it to get that vector whenever we want it.fn get_new_vector() -> Vec<String> { // Return a vector from a function. vec!["a".to_string(), "b".to_string()] } fn main() { let result = get_new_vector(); println!("{:?}", result); }["a", "b"]
Equals
We can compare 2 vectors for equality by using the equality operator. This compares the actual elements, so we are testing logical equality.
fn main() { // Create 2 vectors. let values1 = vec![1, 2, 3]; let mut values2 = Vec::new(); values2.push(1); values2.push(2); values2.push(3); // Test vectors for equality. if values1 == values2 { println!("EQUAL"); } }EQUAL
Often in Rust we have an iterator and want to get a vector from it. The collect()
function, with the TurboFish operator, is helpful here.
map()
on an iterator, and then collect the results into another Vector
of integers.fn main() { let values = vec![1, 2, 3]; // Use collect to convert from an iterator to a vector. let result = values.iter().map(|x| x * 2).collect::<Vec<i32>>(); println!("{:?}", result); }[2, 4, 6]
IsEmpty
It is possible to test the element count of a vector with len()
. The clippy tool will recommend using is_empty()
instead of testing against a zero length.
fn main() { let mut v = vec![]; // Vector is currently empty. if v.is_empty() { println!("EMPTY"); } // Add an element. v.push(10); // No longer empty. if !v.is_empty() { println!("NOT EMPTY"); } }EMPTY NOT EMPTY
Sometimes we want to exchange the positions of 2 elements in a vector. The swap()
method can do this—and it tends to be a clear way of expressing this logic.
fn main() { // Use swap to change the positions of the vector elements. let mut rat = vec!['r', 'a', 't']; println!("{:?}", rat); rat.swap(0, 2); println!("{:?}", rat); }['r', 'a', 't'] ['t', 'a', 'r']
Capacity
benchmarkA significant speedup can be attained by using the with_capacity()
function to create a vector in Rust. We test vector creation here.
with_capacity()
function with an accurate capacity of 100.with_capacity()
can give a nice speedup for vector creation, and is always recommended if any idea about the capacity is known.use std::time::*; fn main() { if let Ok(max) = "10000000".parse::<usize>() { // Version 1: use with_capacity. let t0 = Instant::now(); for _ in 0..max { let mut v = Vec::with_capacity(100); for i in 0..100 { v.push(i); } } println!("{}", t0.elapsed().as_millis()); // Version 2: use new. let t1 = Instant::now(); for _ in 0..max { let mut v = Vec::new(); for i in 0..100 { v.push(i); } } println!("{}", t1.elapsed().as_millis()); } } 916 (with_capacity) 2645 (new)
How can we optimize looping over elements in a vector? It is important to loop up to the len()
of a vector—this helps the compiler optimize better.
len()
in loops.len()
as a top bounds.use std::time::*; fn main() { if let Ok(max_initial) = "10000".parse::<usize>() { // Data for test. let mut data = Vec::with_capacity(max_initial); for i in 0..max_initial { data.push(i); } let mut count1 = 0; let mut count2 = 0; // Version 1: loop over vec except last 2 elements. let t0 = Instant::now(); for _ in 0..100000 { let max = data.len() - 2; for i in 0..max { if data[i] == 0 { count1 += 1; } } } println!("{}", t0.elapsed().as_millis()); // Version 2: create slice without last 2 elements, and loop over it up to len. let t1 = Instant::now(); for _ in 0..100000 { let slice = &data[0..data.len() - 2]; for i in 0..slice.len() { if slice[i] == 0 { count2 += 1; } } } println!("{}", t1.elapsed().as_millis()); println!("{}/{}", count1, count2); } }177 0..max 156 0..slice.len() 100000/100000
It is possible to create 2D vectors, and use even further dimensions, by nesting vectors. This is like a "jagged vector."
It is possible to access a vector element without bounds checking for a speedup. But if possible, try using iter()
or slice-based loops for the same effect in safe code.
We can create and initialize Vecs in many ways in Rust. The macro vec
is most often used, but the other functions like with_capacity()
are also important.