Understanding Dereferencing and References in Rust: A Beginner's Guide

Understanding Dereferencing and References in Rust: A Beginner's Guide
Photo by Bailey Zindel / Unsplash

In Rust, the concepts of dereferencing and getting a reference are fundamental and revolve around how we interact with memory. Let’s break this down in simple terms:

1. References (&)

A reference is a way to "borrow" a value without taking ownership of it. You can create a reference to a value using the & operator.

Example:

let x = 5;
let y = &x; // `y` is a reference to `x`

println!("x: {}, y: {}", x, y); // Output: x: 5, y: 5

Here:

  • &x creates a reference to x.
  • y does not own the value of x; it only points to it.

2. Dereferencing (*)

To access the value behind a reference (i.e., the value the reference points to), you use the dereference operator (*).

Example:

let x = 5;
let y = &x; // `y` is a reference to `x`

println!("Value of x through y: {}", *y); // Output: Value of x through y: 5

Here:

  • *y accesses the value behind the reference y.

Think of dereferencing like saying, "What value is this reference pointing to?"

3. Mutable References (&mut)

If you want to modify a value through a reference, you need a mutable reference. You can create a mutable reference with &mut.

Example:

let mut x = 5;
let y = &mut x; // `y` is a mutable reference to `x`

*y += 1; // Modify the value through the reference
println!("x: {}", x); // Output: x: 6

Here:

  • &mut x creates a mutable reference to x.
  • *y dereferences the mutable reference, allowing you to change the value.

Why References and Dereferencing Matter

Rust uses references to enable safe memory access without ownership conflicts:

  • References let you borrow data temporarily.
  • Dereferencing lets you interact with the actual value a reference points to.

For example:

fn print_value(value: &i32) {
    println!("Value: {}", value);
}

let x = 10;
print_value(&x); // Borrow `x` by reference
  • The function print_value borrows x by reference (&x), avoiding the need to copy or move the value.
  • Inside the function, dereferencing is usually implicit when reading the value.

Ownership and Safety

Rust enforces strict rules:

  • Only one mutable reference or any number of immutable references can exist at a time.
  • This ensures that data is never modified unexpectedly while being read.

Example of conflict:

let mut x = 5;

let y = &x;    // Immutable reference
let z = &mut x; // Mutable reference (error: cannot borrow as mutable because it's already borrowed as immutable)

println!("y: {}, z: {}", y, z);

Key Takeaways

  1. Use & to get a reference to a value.
  2. Use * to dereference and access the value behind a reference.
  3. Use &mut for mutable references to modify values.
  4. Rust ensures memory safety by enforcing strict borrowing rules.

Now I have question.

// The output of

let x = 5;
let y = &x; // y is a reference to x

println!("x: {}, y: {}", x, y); // Output: x: 5, y: 5

// and

let x = 5;
let y = &x; // y is a reference to x

println!("x: {}, y: {}", x, *y); // Output: x: 5, y: 5

// is the same right?

Yes, the output of both code examples is indeed the same, but the way the values are accessed differs.

First Example:

let x = 5;
let y = &x; // `y` is a reference to `x`

println!("x: {}, y: {}", x, y); // Output: x: 5, y: 5
  • Here, y is a reference to x, and when we print y, Rust automatically dereferences it for us when printing.
  • So, when you print y, Rust internally converts y (a reference) to the value it points to (which is 5 in this case).

Second Example:

let x = 5;
let y = &x; // `y` is a reference to `x`

println!("x: {}, y: {}", x, *y); // Output: x: 5, y: 5
  • In this example, we explicitly dereference y using the * operator to access the value y is pointing to.
  • *y is equivalent to accessing x directly because y is a reference to x.

Summary:

  • Both examples print the same values (x: 5, y: 5) because in the first example, Rust automatically dereferences y for you when printing, and in the second example, you manually dereference y using *.
  • The difference is in how the dereferencing happens: one is implicit and the other is explicit.

Support Us