The Basics of Vectors
Simply speaking, a vector is a dynamic array that we can resize at will.
Imagine Frodo preparing his backpack for the journey. He needs to store various items in his backpack. If he knew from the beginning how many items will be put in, the backpack could be represented by an array. But since Frodo actually doesn't know how many items he will pack along the way, it's better to have it represented as a vector.
fn main() {
// Creating an empty Vector
let mut frodos_backpack = Vec::new();
// Adding an unknown number of items to the Vector
frodos_backpack.push("Lembas Bread");
frodos_backpack.push("Elven Cloak");
frodos_backpack.push("Sting - the sword");
println!("Frodo's Vector-Backpack: {:?}", frodos_backpack);
// Creating a Vector using a macro
let frodos_backpack_2 = vec!["Lembas Bread", "Elven Cloak", "Sting - the sword"];
println!("Frodo's Vector-Backpack: {:?}", frodos_backpack_2);
}
There are two ways of creating a vector.
Vec::new()
creates a new, empty Vector. Thepush
method adds items to Frodo's backpack, just as he would gather supplies for his journey.vec!
macro enables us to initialize a vector in just one line.
Accessing Elements
Just as members of the Fellowship have different roles, elements in a Vector can be accessed and utilized differently.
Frodo needs to consult different members of the Fellowship for advice.
fn main() {
let fellowship = vec!["Gandalf", "Aragorn", "Legolas", "Gimli"];
// Accessing elements using indexing
let wizard = fellowship[3];
println!("The wizard in the Fellowship is: {}", wizard);
// Accessing elements using get method for safety
match fellowship.get(3) {
Some(member) => println!("The fifth member is: {}", member),
None => println!("There is no fifth member."),
}
}
By using fellowship[3]
, Frodo can access the first element directly. But what if a member with such an index doesn't exist? We would get an error.
On the other hand, the get
method provides a safer way to access elements because it returns None
if the index is out of bounds, like seeking a member not present in the Fellowship.
Iterating Over Vectors
We can iterate over vectors so that we get access to each element.
Let's imagine that the Ents decide to march against Saruman and each of them wants to announce it.
fn main() {
let ents = vec!["Treebeard", "Quickbeam", "Beechbone"];
// Iterating over the Ents
for ent in ents {
println!("I, {}, am marching to Isengard!", ent);
}
// The ents vector is consumed, so this would error out:
// println!("{:?}", ents);
}
After the loop, the vector ents
is no longer usable because it has been consumed by the iteration. The vector's ownership has been transferred to the loop.
If we want to keep the vector intact, we can use the iter
method:
fn main() {
let ents = vec!["Treebeard", "Quickbeam", "Beechbone"];
// Iterating over the Ents
for ent in ents.iter() {
println!("I, {}, am marching to Isengard!", ent);
}
// The ents vector is intact:
println!("{:?}", ents);
}
Now, ents.iter()
creates an iterator that borrows each element of the vector. The for
loop iterates over these references. After the loop, the vector ents
can still be used since its ownership has not been affected by the iteration.
Iterating with indexes
We can also keep track of the position of iterated items using .enumerate()
.
Imagine the members of the Fellowship each taking a turn to watch over the camp at night. Their order is crucial because each has a different task or time to start their watch.
fn main() {
let fellowship = vec!["Frodo", "Sam", "Merry", "Pippin"];
// Iterating over the Fellowship members with their watch order
for (index, member) in fellowship.iter().enumerate() {
println!("{} takes the watch at hour {}", member, index + 1);
}
}
Here, fellowship.iter().enumerate()
creates an iterator that yields pairs: the index of each element (starting from 0) and a reference to the element itself. This is very useful when you interact with vector elements and need to know their position.
Modifying Elements
Just as characters in Middle-Earth grow and change, elements in a Vector can be modified.
The sword Narsil is reforged into Andúril, symbolizing transformation. Similarly, elements in a Vector can be transformed.
fn main() {
let mut weapons = vec!["Narsil", "Glamdring", "Sting"];
// Reforging Narsil into Andúril
weapons[0] = "Andúril";
println!("The reforged weapons: {:?}", weapons);
}
Here, modifying the first element of the weapons
Vector symbolizes the reforging of Narsil into Andúril. Notice that the Vector was initialized as mutable by using let mut
.
Using Enumerations with Vectors
In Middle-Earth, beings of different races unite for a common cause. Similarly, Rust's enumerations (the enum
type) allow Vectors to store elements of different types.
A council meeting is held with representatives of different races.
enum MiddleEarthBeing {
Human(String),
Elf(String),
Dwarf(String),
Hobbit(String),
}
fn main() {
let council_members = vec![
MiddleEarthBeing::Human("Aragorn".to_string()),
MiddleEarthBeing::Elf("Legolas".to_string()),
MiddleEarthBeing::Dwarf("Gimli".to_string()),
MiddleEarthBeing::Hobbit("Frodo".to_string()),
];
// Discussing the plan to defeat Sauron
for member in council_members {
match member {
MiddleEarthBeing::Human(name) => {
println!("{} says we should use strategy.", name)
}
MiddleEarthBeing::Elf(name) => {
println!("{} suggests an alliance with the Elves.", name)
}
MiddleEarthBeing::Dwarf(name) => {
println!("{} recommends gathering weapons.", name)
}
MiddleEarthBeing::Hobbit(name) => {
println!("{} offers to take the Ring.", name)
}
}
}
}
This snippet demonstrates how Enums can be combined with Vectors to represent a diverse group, each with its unique characteristics and contributions.
Vector Capacity and Reallocation
The journey through Middle-Earth is unpredictable, requiring flexibility in plans and resources. Similarly, a Vector in Rust has a capacity that can change dynamically.
As the Fellowship prepares for its journey, the members must ensure they have enough supplies, much like managing the capacity of a Vector.
fn main() {
let mut supplies = Vec::with_capacity(5);
supplies.push("Lembas Bread");
supplies.push("Elven Rope");
// ... more items are added
println!("Total supplies: {}", supplies.len());
println!("Capacity of the backpack: {}", supplies.capacity());
}
In this example, Vec::with_capacity(5)
creates a Vector with an initial capacity for 5 items, ensuring the Fellowship has a pre-determined space for essential supplies. As items are added, Rust automatically increases the capacity if needed, just as the Fellowship might acquire more supplies along their journey.
Without pre-allocating capacity, every time the vector exceeds its current capacity, it needs to reallocate memory to accommodate additional elements. This reallocation involves allocating new memory, copying the existing elements to the new memory location, and then deallocating the old memory. This can be an expensive operation, especially if it happens frequently.
Also, note that the capacity
of a vector is the amount of memory allocated for elements, while the length
(or len()
) is the number of elements currently in the vector.
Slicing Vectors
Like choosing the right path in the Mines of Moria, slicing a Vector in Rust involves selecting a specific portion for focused operations.
The Fellowship decides to divide their responsibilities.
fn main() {
let fellowship = vec!["Frodo", "Sam", "Gandalf", "Aragorn", "Legolas", "Gimli", "Boromir"];
// Creating a slice for the Hobbits
let hobbits = &fellowship[0..2]; // Frodo and Sam
println!("Hobbits in the Fellowship: {:?}", hobbits);
}
Here, slicing the fellowship
Vector allows for focusing on just the Hobbits, leaving the others behind.
Removing Elements
In Middle-Earth, certain allies and enemies may leave the journey, just as elements can be removed from a Vector in Rust.
Consider a scenario where the Fellowship must part ways with one of its members.
fn main() {
let mut fellowship = vec!["Frodo", "Sam", "Gandalf", "Aragorn", "Legolas", "Gimli", "Boromir"];
// Boromir's departure
let departed = fellowship.pop(); // Removes the last element
println!("Departed member: {:?}", departed);
println!("Remaining Fellowship: {:?}", fellowship);
}
The pop
method removes the last element of the Vector. It returns an Option
, which is Some(value)
if an element was removed, or None
if the Vector was empty, reflecting the uncertainty of partings in Middle-Earth.
Concatenating Vectors
In the saga of Middle-Earth, different groups often come together to form a larger force, much like how Vectors can be concatenated in Rust to form a larger collection.
Imagine various groups in Middle-Earth uniting for a significant battle.
fn main() {
let elves = vec!["Legolas", "Thranduil"];
let dwarves = vec!["Gimli", "Thorin"];
// Uniting Elves and Dwarves
let united_army = [elves, dwarves].concat();
println!("United army: {:?}", united_army);
}
The concat
method merges multiple Vectors into one. The united_army
Vector combines the elements of the elves
and dwarves
Vectors. This method is pretty useful when you need to join elements from different sources into a new Vector.
Extending Vectors
As alliances are formed in Middle-Earth, bringing together different factions for a common purpose, Rust allows the combination of Vectors using the extend
method. It sounds similar to concat
, but the difference is that we append all elements of one Vector to another, enhancing the first Vector without creating a new one.
Imagine the armies of Elves and Dwarves joining the forces of Aragorn to stand united against the forces of darkness.
fn main() {
let mut army_of_men = vec!["Aragorn"];
let elves_and_dwarves = vec!["Legolas", "Gimli"];
// The Elves and Dwarves join the coalition
army_of_men.extend(elves_and_dwarves);
println!("United Army: {:?}", army_of_men);
}
The extend
method adds the elements of elves_and_dwarves
into army_of_men
.
Clearing Vectors
Just as the One Ring was ultimately destroyed in Mount Doom, clearing all traces of its power, a Vector in Rust can be completely cleared of its elements.
Imagine a scenario where, after the final battle, the heroes discard their weapons to celebrate the end of the war.
fn main() {
let mut weapons = vec!["Andúril", "Sting", "Glamdring"];
// Discarding all weapons
weapons.clear();
println!("Weapons after the war: {:?}", weapons);
}
The clear
method removes all elements from the weapons
Vector, leaving it empty. This action reflects the notion of putting aside arms and tools of war, as Middle-Earth enters a period of peace. The clear
method is useful for resetting a Vector for reuse, without reallocating new memory.