Lesson 3
Understanding Variable References and Mutable References in Rust
Lesson Goal and Plan

Hello, Coders! Today, we'll examine two intriguing Rust concepts: variable references and mutable references, essential for efficient memory management. You'll learn about them through an example of Rust code employing mutable references. Off we go!

Introduction to Variable References

Imagine we're stepping into the world of Rust's pointers, which act as directional signs to memory locations, much like addresses in a neighborhood.

A variable reference is like having a house address—it guides you to where the house is located. Just as you can visit and look at the house based on its address, but not alter it, a variable reference allows you to see a value without changing it.

To create a reference to a variable, add & before the variable name. To dereference a variable and get the value of a reference variable, add * before the variable name.

Here's how we might express this in Rust:

Rust
1fn main() { 2 let x = 5; 3 let y = &x; // y is a reference to the memory location of x 4 let z = *y; // dereference y 5 println!("The value at y is {}", z); // Prints: "The value at y is 5" 6}

In this case, y has the "address" that points to where x is in memory, much like having directions to a house. z contains the value stored at y.

Introduction to Mutable References

Consider this scenario in Rust:

Rust
1fn main() { 2 let mut x = 5; 3 let mut y = x; 4 y += 1; 5 println!("x is {}, y is {}.", x, y); // Prints: "x is 5, y is 6." 6}

Here, we start by creating a mutable variable x with a value of 5. We then copy the value of x into a new variable y. When we increase the value of y by 1, x remains unchanged. The reason is that y is an entirely separate copy of x's value. It's like if you copied your friend's house key; changing the lock on your own house doesn't change the lock on your friend's house.

Now, let's look at mutable references:

Rust
1fn main() { 2 let mut x = 5; 3 let y = &mut x; 4 *y += 1; 5 println!("x is {}", x); // Prints: x is 6 6}

In sharp contrast with our earlier code, y here is not a copy of x's value but a mutable reference to x—it's as if you have the key to actually unlock and alter what's inside x's house.

In our house metaphor, if the variable reference is like knowing the address of a house, then *y is akin to opening the door with a key and stepping inside. Once inside, you can rearrange the furniture or repaint the walls, essentially altering the state of the house's interior.

If we then execute an action, like *y += 1;, we're not just looking at the house from the outside, we're changing something inside the house – specifically, we're incrementing the value that x holds. So, x's value changes because we've used our key (the mutable reference pointed to by y) to go inside and make an update.

Mutable References with Functions

When we're using mutable references with functions, we're essentially giving those functions the key to our house and permission to change the interior. Keep in mind, this is temporary. The function only has this power within its scope of operation, which is like a scheduled time when they're allowed to come in and make changes.

Let's take a closer look at our Rust code:

Rust
1fn add_one(x: &mut i32) { // function taking a mutable reference to an i32 2 *x += 1; // increments the value the reference points to 3} 4 5fn main() { 6 let mut num = 5; // our mutable variable 7 add_one(&mut num); // pass a mutable reference to our variable 8 println!("The value of num is: {}", num); // prints: The value of num is: 6 9}
  1. fn add_one(x: &mut i32) { — Here, we declare add_one as a function that takes a mutable reference to a 32-bit integer.
  2. *x += 1; — We increment the value to which the mutable reference x points.
  3. let mut num = 5; — We declare a mutable integer num and assign it the value 5.
  4. add_one(&mut num); — We call the add_one function and pass it the mutable reference of num.
  5. println!("The value of num is: {}", num); — We print the new value of num, which is now 6, thanks to our add_one function.
Mutable References and Scope

Here's how the concept of scope connects with mutable references:

In the main function, we have the num variable — let's think of it as our own house. We're comfortable letting add_one temporarily hold the key to our house (the mutable reference to our num variable) because we trust that add_one will only make the specific change we have agreed on.

This agreement is referred to as 'scope'. The add_one function has a scope that is clearly defined by its function body. It can only make changes within this scope.

Once add_one does its job, that is, increments our num variable by one, its scope ends. It steps out, hands the key back, and it can no longer make any changes to our house. The key no longer works beyond the end of the add_one function, and the mutable reference it once had is no longer valid.

This mechanism is crucial for preserving data integrity as it ensures that changes made by functions are controlled, traceable, and limited to the intended scope. We rest assured that our house – or variable value – only changes when and where we expect.

Lesson Summary

Great job! You've just explored variable references and mutable references in Rust. Reflect on these concepts and practice them some more. Up next, you'll be able to apply your newfound knowledge in hands-on exercises. Practicing will propel your Rust coding skills!

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.