Rusty Rules: Understanding Rust's Borrow Checker

Ensuring Safe Code with Rust's Borrow Checker

Rust is a programming language that prides itself on being fast, safe, and reliable. One of the ways Rust ensures safety is through its borrow checker. But what exactly is the borrow checker, and how does it work? In this blog, we'll explore Rust's borrow checker rules, using simple analogies. This is the continuation of my previous blog post where I discussed the high-level overview of borrow checker.

Ownership: The Boss

To understand the borrow checker, we first need to understand ownership in Rust. In Rust, every value has an owner. The owner is like a boss who controls the value and the memory it occupies. When the owner goes out of scope, Rust frees the memory associated with it, preventing memory leaks.

This means that when a value is assigned to a new variable, the ownership of the value is transferred to the new variable and the old variable cannot be used to access that value anymore.

Think of it like a landlord who owns an apartment. When the lease is up, the landlord takes back the keys, and the apartment is no longer accessible.

Moving In: Transfer of Ownership

Ownership can be transferred from one owner to another using the move semantics. When you assign a value to a new variable, you're transferring the ownership from the previous owner to the new owner. The old owner can no longer access the value.

It's like giving your apartment keys to a new tenant. Once they have the keys, you can no longer enter the apartment.

Borrowing: Sharing is Caring

Borrowing, on the other hand, is when you give temporary access to a value to another variable without transferring ownership.

Think of it like lending your apartment keys to a friend. They can use the apartment, but they don't own it. In Rust, you can create references to a value, which allows borrowing. References are like photocopies of the apartment keys. Your friend can use the apartment, but they can't make any changes to it.


Rust's Rules: Keeping Borrowing in Check

Now that we understand ownership and borrowing let's take a look at Rust's set of borrowing rules:

  • Each value in Rust has a variable that is its owner.
fn main() {
    let x = 5; // x is the owner of the value 5
    println!("The value of x is: {}", x);
}
Rule 1
In this example, we create a variable x and assign it the value 5. x is the owner of the value 5. When x goes out of scope (which happens automatically at the end of the main() function), Rust frees the memory associated with x and the value 5.
  • There can only be one mutable reference to a variable at a time.
let mut x = 5;
let y = &mut x;
let z = &mut x; // error: cannot borrow `x` as mutable more than once at a time
Rule 2
In this example, we create a mutable variable x with a value of 5. We then create two mutable references y and z to x. However, the borrow checker will throw an error because there can only be one mutable reference to a variable at a time.
  • There can be multiple immutable references to a variable at a time.
let x = vec![1, 2, 3];
let y = &x; // First immutable reference
let z = &x; // Second immutable reference (valid)
rule 3
In this example, we create a vector x and then create two immutable references y and z to x. Both y and z can access the data in x without modifying it, because they are immutable references. This is allowed because Rust's borrow checker enforces the rule that there can be multiple immutable references to a variable at a time.
  • References must always be valid. Rust will not allow a reference to a variable that has gone out of scope.
fn main() {
    let reference_to_nothing = dangling_reference();
    println!("This will not print: {}", reference_to_nothing);
}

fn dangling_reference() -> &String {
    let s = String::from("Hello, world!");
    &s
}
rule 4
In this example, we create a function dangling_reference that creates a new String and returns a reference to it. However, we immediately drop the String by letting it go out of scope, which means that the reference returned by the function points to invalid memory. When we try to print the value of the reference in the main function, Rust will throw an error because the reference is no longer valid.
  • Variables cannot be borrowed as mutable and immutable at the same time.
fn main() {
    let mut x = 5;
    let y = &x;
    let w = &x; // valid 
    let z = &mut x; // error: cannot borrow `x` as mutable because it is also borrowed as immutable
}
rule 5
In this example, we first create a mutable variable x with a value of 5. We then create an immutable reference y to x. This is allowed because there can be multiple immutable references to a variable at a time. However, when we try to create a mutable reference z to x, the borrow checker will throw an error because x has already been borrowed as immutable by y, and Rust does not allow borrowing a variable as both mutable and immutable at the same time.

This rule makes sense because if a variable has both mutable and immutable references, there is a possibility that the mutable reference could change the value of the variable, while the immutable reference is still pointing to the previous value. This could lead to unexpected behavior and errors in the code.


Thanks for reading!