Skip to main content
  1. Posts/

Rust: & vs * (references and dereferencing)

·535 words·3 mins
Author
m58
0xm58.xyz
Table of Contents
One creates a reference, the other follows it. Here’s how & and * work and when to use each.

In Rust, & and * show up everywhere. They’re easy to mix up at first.

What & does: references (borrowing)
#

& creates a reference to a value. You’re not taking ownership or copying; you’re borrowing it.

let x = 42;
let r = &x;   // r is a reference to x
  • r has type &i32: “reference to i32”.
  • r does not own the 42; it points to x. When r goes out of scope, nothing is dropped; only the reference disappears.

References are immutable by default. To allow changes through the reference, you need a mutable reference &mut:

let mut x = 42;
let r = &mut x;
*r += 1;   // we need * to get at the value (see below)

You can have many &T references at once, or one &mut T, following Rust’s borrowing rules.

What * does: dereferencing
#

* dereferences a reference: it follows the pointer and gives you the value it points to.

let x = 42;
let r = &x;
assert_eq!(*r, 42);   // *r is the value 42

So:

  • & → “take a reference to this”
  • * → “give me the value this reference points to”

Without *, you’re working with the reference (the “pointer”); with *, you’re working with the underlying value.

When do you need *?
#

When the type doesn’t match, the compiler often forces you to dereference.

fn add_one(n: &i32) -> i32 {
    *n + 1   // *n gives i32 so we can add 1
}

Here n is &i32. The expression n + 1 would be invalid (reference + integer). So you write *n + 1 to get an i32 and then add 1.

Method calls usually don’t need *. Rust’s “method receiver deref coercion” applies dereferences for you:

let s = String::from("hello");
let r = &s;
println!("{}", r.len());   // no * needed; r is auto-dereferenced

So you use * when you need the value as a value (e.g. in expressions, assignments, or when passing to a function that expects T and you have &T).

Putting it together
#

SymbolNameMeaning
&xReferenceBorrow x; type is &T
&mut xMutable refBorrow x mutably; type is &mut T
*rDereferenceThe value r points to; type is T

Example that uses both:

fn double(n: &mut i32) {
    *n *= 2;   // dereference to read and write the value
}

let mut x = 5;
double(&mut x);   // pass a mutable reference
assert_eq!(x, 10);
  • &mut x creates a mutable reference to x.
  • Inside double, *n is the actual i32, so *n *= 2 modifies the value in place.

Summary
#

  • &: create a reference (borrow). Types like &T, &mut T, or when calling foo(&x).
  • *: dereference. Follow the reference to get the value. Use it when you need a T and you have &T or &mut T.

Once you read & as “reference to” and * as “value pointed to,” the rest is mostly the borrow checker and a bit of practice.

Next: Maybe Box, Rc, and RefCell in a later post: other ways to own or share data without raw pointers.

Related

About

·113 words·1 min
DevSecOps, pentesting, blue team. Writeups and tooling. Who # Robin Marchand (m58). I work on DevSecOps, pentesting and red team, and blue team: detection, response, hardening.