Getting Started with Rust Using Rustlings — Part 4: Vectors

Jesse Verbruggen
4 min readFeb 14, 2023
Rustlings Part 4: Vectors

In my last parts, I reviewed If and Primitive Types in Rust. This is Part four of my miniseries on Rust. If you want to read the last part, you can get to it by following the link to my article below.

In this series, I am going over my process of learning the syntax of Rust with Rustlings. In my first Part, I explain how to install Rust and Rustlings. So if you haven’t yet done that, please navigate there to follow along.

Vectors

What are Vectors? A vector is a big container holding many things, like a toy box.

Imagine you have a toy box in which you can put your toys, and you want to keep them organized. A vector is like having a big toy box with different compartments where you can set different kinds of toys in each compartment.

In the same way, a Rust vector can hold many values, and you can put different types of values in it, like numbers or text or even other vectors. Like a toy box, you can organize your values in a vector in different compartments called “indices” in Rust.

Rust’s vectors differ from arrays because they can grow or shrink in size as you add or remove elements. This makes them very versatile for situations where you don’t know how many elements you’ll need to store ahead of time.

You can add elements to a vector using the push method, and remove elements using the pop method. You can also access elements in a vector using their index number, which starts at zero.

Rust vectors are very efficient and safe to use. The Rust compiler will check that you’re not accessing elements that don’t exist, which can help prevent bugs in your code. And since vectors are allocated on the heap, they can hold large amounts of data without running into memory issues.

Now you should have a basic understanding of vectors. Let’s move on to the exercises.

vecs1.rs

fn array_and_vec() -> ([i32; 4], Vec<i32>) {
let a = [10, 20, 30, 40]; // a plain array
let v = // TODO: declare your vector here with the macro for vectors

(a, v)
}

This exercise aims to use the vec! macro to initialise a new vector with the same values as the array. To do this, we will write vec![10, 20, 30, 40] to initialize v.

fn array_and_vec() -> ([i32; 4], Vec<i32>) {
let a = [10, 20, 30, 40]; // a plain array
let v = vec![10, 20, 30, 40];

(a, v)
}

That’s all we need to do to solve this exercise. We could also have created the vector using the Vec::new() syntax and pushed the values to this vector one by one, but this macro saves us time.

vecs2.rs

fn vec_loop(mut v: Vec<i32>) -> Vec<i32> {
for i in v.iter_mut() {
// TODO: Fill this up so that each element in the Vec `v` is
// multiplied by 2.
???
}

// At this point, `v` should be equal to [4, 8, 12, 16, 20].
v
}

fn vec_map(v: &Vec<i32>) -> Vec<i32> {
v.iter().map(|num| {
// TODO: Do the same thing as above - but instead of mutating the
// Vec, you can just return the new number!
???
}).collect()
}

This exercise teaches us how to iterate over the items of the vector, mutate its items, and create a new vector from an existing one.

The first method vec_loop will mutate the passed vector and return the result. This will not affect the original vector and only use a single vector to perform its operations. We will put the result of our operation back into the vector we are iterating over as follows. I will also rename some variables to make the code more readable.

fn vec_loop(mut vector: Vec<i32>) -> Vec<i32> {
for element in vector.iter_mut() {
*element *= 2;
}

vector
}

So as you can see, we can change the element in place. Note that I am using the *= operator, which multiplies the value of the left side of the operator with the right side of the operator. In this scenario, we iterate over the vector’s elements and mutate them. Therefore we use the iter_mut function on the vector.

Now the next part is easier to write, but the key difference here is that v.iter_mut() does not create a new vector while v.iter().map() does create a new vector. That requires v.iter().map() more memory, which is something to remember when working with large amounts of data.

fn vec_map(v: &Vec<i32>) -> Vec<i32> {
v.iter().map(|num| {
num * 2
}).collect()
}

With these two solutions, the problems are solved. The following exercises require a deeper understanding of Ownership, References and Borrowing in Rust. Which I explain there.

If I helped you learn more about Rust or you found this interesting, please clap 👏 and share this article to help more people find these articles.

Let’s help each other learn and grow! Peace out✌️

--

--