Operators and Expressions

Dereference Operator

The unary prefix dereference operator (*) follows a reference (&T, &mut T), smart pointer (Box<T>), or raw pointer (*const T, *mut T) to access the underlying memory location (place) that the pointer points to.

Following References with *

When a variable r holds a reference &i32 or &mut i32, r itself stores a memory address, while *r refers to the i32 value at that address:

Stack
+---------------+         +---------------+
| r: &mut i32   | ------> | x: i32 = 10   |
| (holds addr)  |         | (*r accesses  |
+---------------+         |  this slot)   |
                          +---------------+
fn main() {
    let mut x: i32 = 10;

    // 1. Read through a shared reference `&i32` using `*`:
    let r_shared: &i32 = &x;
    assert_eq!(*r_shared, 10);
    println!("Read via *r_shared: {}", *r_shared);

    // 2. Write (mutate) through a mutable reference `&mut i32` using `*`:
    let r_mut: &mut i32 = &mut x;
    *r_mut += 25;
    println!("Mutated via *r_mut: {}", *r_mut);

    println!("Final x = {x}");
}
Read via *r_shared: 10
Mutated via *r_mut: 35
Final x = 35

Multi-Level Dereferencing (**r)

Each * strips away one layer of indirection. If you have a reference to a reference (&&T), *r has type &T, and **r reaches the underlying T:

fn main() {
    let value: i32 = 42;
    let level_1: &i32 = &value;
    let level_2: &&i32 = &level_1;

    println!("*level_1  = {}", *level_1);
    println!("**level_2 = {}", **level_2);
}
*level_1  = 42
**level_2 = 42

When Explicit * Is Required vs Automatic Dereferencing

Because the dot operator (.) for method calls and field access automatically dereferences its receiver (r.len() becomes (*r).len()), you do not need * for method calls or struct field access.

However, you must write explicit * in three common situations:

Situation Why Explicit * Is Required Example
Assigning a new value to the target of &mut T Writing r = val would try to reassign the reference variable r itself instead of the target memory *r = 100; or *r += 1;
Arithmetic or bitwise operations on a referenced primitive Binary operators like + or * on &i32 + i32 or &mut i32 require the value let sum = *r + 10;
Copying a Copy value out of &T to own it Converts &T into an owned T let owned: i32 = *r;
fn double_in_place(slot: &mut i32) {
    // Explicit `*` is required on both sides:
    *slot = *slot * 2;
}

fn main() {
    let mut n = 21;
    double_in_place(&mut n);
    println!("n = {n}");
}
n = 42

Note on Operator Precedence: The dot operator (.) has higher precedence than unary *! That means *wrapper.value is parsed as *(wrapper.value). If wrapper is a reference to a struct whose field is not a pointer, wrapper.value auto-dereferences wrapper automatically; if you ever need to dereference the receiver explicitly before accessing a tuple index or method, wrap it in parentheses (*r).0.

Ownership Rules When Dereferencing (E0507)

When you dereference a reference *r on the right-hand side of an assignment (let val = *r;):

  • If T implements Copy (such as i32, f64, bool, char), *r copies the bytes out of the target into val.
  • If T does not implement Copy (such as String or Vec<T>), let val = *r; attempts to move ownership of the target value out from behind a borrowed reference! Because a reference (&T or &mut T) does not own the target, Rust forbids moving a non-Copy value out of a reference with E0507:
// ❌ does not compile
fn main() {
    let text = String::from("owned string");
    let r: &String = &text;

    let stolen: String = *r;
}
error[E0507]: cannot move out of `*r` which is behind a shared reference
 --> src/main.rs:5:26
  |
5 |     let stolen: String = *r;
  |                          ^^ move occurs because `*r` has type `String`, which does not implement the `Copy` trait
  |
help: consider cloning the value if the performance cost is acceptable
  |
5 -     let stolen: String = *r;
5 +     let stolen: String = r.clone();

error: aborting due to 1 previous error

To obtain an owned value from &String or &mut String, either call r.clone() (to make a copy) or, if you have a &mut String and want to take its contents while leaving an empty String behind, use std::mem::take(r) or std::mem::replace(r, new_val).

Custom Smart Pointers and the Deref Trait

Beyond built-in references (&T and &mut T), the * operator also works on smart pointers such as Box<T>, Rc<T>, Arc<T>, and MutexGuard<T>. For any type that implements std::ops::Deref (and DerefMut for mutable dereferencing), writing *smart_ptr invokes *(smart_ptr.deref()):

fn main() {
    let heap_num: Box<i32> = Box::new(50);

    // `*heap_num` follows the heap pointer inside `Box<i32>` to read `50`:
    assert_eq!(*heap_num, 50);
    println!("Dereferenced Box: {}", *heap_num);
}
Dereferenced Box: 50