Skip to main content
  1. Posts/

Rust: & vs * (references and dereferencing)

·569 words·3 mins
Author
m58
0xm58.xyz
Table of Contents
& borrows a value. * follows a reference to reach the value behind it. The syntax is small, but it sits right in the middle of Rust’s ownership model.

If references feel awkward at first, that is normal. In Rust they are not just pointers with nicer spelling. They are part of the language’s safety model.

& creates a reference
#

When you write &x, you borrow x:

let x = 42;
let r = &x;

Now:

  • x has type i32
  • r has type &i32

r does not own the value. It just refers to it.

This matters because borrowing avoids moves:

fn print_len(s: &String) {
    println!("{}", s.len());
}

let name = String::from("m58");
print_len(&name);
println!("{name}");

name is still usable after the function call because the function borrowed it instead of taking ownership.

&mut creates a mutable reference
#

If a function needs to modify a value without taking ownership, use &mut:

fn bump(n: &mut i32) {
    *n += 1;
}

let mut value = 10;
bump(&mut value);

Now the function can change the caller’s value in place.

Rust limits mutable borrowing aggressively:

  • you can have many immutable references &T
  • or one mutable reference &mut T
  • but not both at the same time

That rule prevents data races and aliasing bugs.

* dereferences a reference
#

When you have a reference and need the value behind it, use *:

let x = 42;
let r = &x;

assert_eq!(*r, 42);

Think of it like this:

  • &x means “borrow x
  • *r means “follow r to the thing it points to”

When do you actually need *?
#

You need explicit dereferencing when the compiler expects the underlying value and not the reference.

fn add_one(n: &i32) -> i32 {
    *n + 1
}

n is a reference, so n + 1 does not make sense. *n + 1 does.

You also need * when mutating through a mutable reference:

fn double(n: &mut i32) {
    *n *= 2;
}

Without *, you would be trying to multiply the reference itself.

Why method calls often do not need *
#

Rust applies automatic dereferencing in method calls:

let s = String::from("hello");
let r = &s;

println!("{}", r.len());

Even though r is &String, the method call works because Rust dereferences it for you.

That is why beginners often see * in arithmetic or assignment, but not in simple method calls.

References to slices are everywhere
#

In real Rust code you will often borrow slices instead of concrete collection types:

fn sum(values: &[i32]) -> i32 {
    let mut total = 0;

    for value in values {
        total += *value;
    }

    total
}

Here values is a borrowed slice, and value inside the loop is a borrowed &i32.

That style is common because it makes functions more flexible. A slice parameter can accept arrays, vectors, and other contiguous collections.

A quick mental model
#

If you want something simple to remember:

  • T is an owned value
  • &T is a shared borrow
  • &mut T is an exclusive mutable borrow
  • *ref accesses the value behind a reference

That mental model is enough for most beginner code.

Summary
#

  • & borrows a value without taking ownership.
  • &mut borrows a value mutably so it can be changed in place.
  • * dereferences a reference when you need the underlying value.
  • Method calls often hide dereferencing through automatic coercions.

Once & and * make sense, ownership and borrowing become much easier to learn.

Related

Rust basics: Option and Result

·481 words·3 mins
Rust does not use null for ordinary absence, and it does not rely on exceptions for recoverable errors. Instead, it uses enums: Option<T> and Result<T, E>. These two types show up everywhere. If you understand them early, a lot of Rust APIs stop looking strange.