Question

Rust References: What & and * Mean

rustsyntaxreferencedereference

Question

What do the & and * symbols mean in Rust, and what exactly is a Rust reference?

For example, why do both of these programs print 6?

fn main() {
    let c: i32 = 5;
    let rc = &c;
    let next = rc + 1;
    println!("{}", next);
}
fn main() {
    let c: i32 = 5;
    let rc = &c;
    let next = *rc + 1;
    println!("{}", next);
}

However, when using a mutable reference, the following does not compile:

fn main() {
    let mut c: i32 = 5;
    let next: i32;

    {
        let rc = &mut c;
        next = rc + 1;
    }

    println!("{}", next);
}

Rust reports that + cannot be applied to &mut i32, but this version works:

fn main() {
    let mut c: i32 = 5;
    let next: i32;

    {
        let rc = &mut c;
        next = *rc + 1;
    }

    println!("{}", next);
}

Why does &i32 + 1 work in this case while &mut i32 + 1 does not? Does Rust implicitly dereference immutable references but not mutable references?

Short Answer

Rust references let code access a value without taking ownership of it. You will learn that & creates a borrow, * accesses the value behind a reference, and that the apparent automatic dereference in &i32 + 1 is actually an operator-trait implementation—not a general implicit dereference rule.

Concept

A reference is a non-owning, temporary way to access a value. References are central to Rust because they allow code to share or modify data while Rust's borrow checker prevents dangling references and unsafe aliasing.

  • &value creates an immutable reference, with type &T.
  • &mut value creates a mutable reference, with type &mut T.
  • *reference dereferences a reference: it accesses the referenced value.
let value = 5;
let reference = &value; // reference: &i32
let copied = *reference; // copied: i32

For primitive integer types, i32 implements Copy. Therefore, reading *reference copies the integer instead of moving it.

Borrowing rules

Rust applies two important rules while references are alive:

  1. You can have any number of immutable references (&T).
  2. You can have exactly one mutable reference (&mut T), and no immutable references to that value at the same time.

An immutable reference lets you read. A mutable reference lets you read and write.

Why rc + 1 works for &i32

Rust does not generally insert * before every reference in an expression. Instead, operators such as + are implemented through traits.

Conceptually, this expression:

rc + 1

is evaluated using the std::ops::Add trait. Rust's standard library provides implementations that allow references to primitive numeric types, such as &i32, to be added to an i32. So this works:

let rc: &i32 = &5;
let result = rc + 1;

That behavior is specific to available trait implementations. It is not evidence that Rust automatically dereferences every immutable reference for every operation.

The standard library does not provide the corresponding Add<i32> implementation for &mut i32. A mutable reference is a distinct type, so this fails:

let mut value = 5;
let reference: &mut i32 = &mut value;
// let result = reference + 1; // no Add implementation for &mut i32

Writing *reference + 1 explicitly produces an i32, and i32 does implement addition with i32.

let result = *reference + 1;

Rust does perform deref coercions in selected places, especially when passing arguments to functions or calling methods. Binary operators are trait-based, so you should not rely on deref coercion to make arbitrary operator expressions work.

Mental Model

Think of a value as an object in a locked storage box.

  • The variable that owns the value has the box itself.
  • &value gives someone a read-only visitor pass to the box.
  • &mut value gives someone the only edit pass to the box.
  • *reference means: “open the box through this pass and access the item inside.”

A &i32 and &mut i32 can both point at an integer, but their permissions differ. They are also different Rust types. Just because a numeric operation accepts one kind of pass does not mean it accepts the other.

For &i32 + 1, imagine that the standard library has a convenience rule saying, “A read-only pass to an integer may participate in addition.” It does not provide the same convenience rule for an edit pass (&mut i32). When in doubt, open the box explicitly with *.

Take Quiz

Syntax and Examples

Core syntax

let value = 10;
let read_ref = &value;       // &T: immutable borrow
let copied_value = *read_ref; // dereference and read the i32

let mut count = 10;
let write_ref = &mut count;  // &mut T: mutable borrow
*write_ref += 1;             // dereference and modify

Immutable references: &T

fn print_number(number: &i32) {
    println!("{number}");
}

fn main() {
    let score = 42;
    print_number(&score);

    println!("Score is still {score}");
}

print_number borrows score; it does not own it. The original variable remains usable after the call.

Mutable references: &mut T

fn add_one(number: &mut i32) {
    *number += 1;
}

fn main() {
    let mut score = 42;
    add_one(&mut score);

    println!("{score}"); // 43
}

number has type &mut i32. The *number expression accesses the actual i32, allowing += 1 to update it.

Reading through either reference kind

fn main() {
    let value = 5;
    let read_ref = &value;
    println!("{}", *read_ref);

    let mut other_value = 8;
    let write_ref = &mut other_value;
    println!("{}", *write_ref);
}

Both &T and &mut T can be dereferenced for reading. Only &mut T can be used to modify the referred value.

Operator detail

fn main() {
    let value = 5;
    let read_ref = &value;

    let a = read_ref + 1;  // supported Add implementation for &i32
    let b = *read_ref + 1; // i32 + i32

    assert_eq!(a, 6);
    assert_eq!(b, 6);
}

The second form is the more direct expression of “read the referenced integer, then add one.”

Step by Step Execution

Trace this program:

fn main() {
    let mut total = 5;
    let reference = &mut total;
    *reference += 1;
    let next = *reference + 1;
    println!("{next}");
}
  1. let mut total = 5; creates an owned, mutable i32 named total.
  2. let reference = &mut total; creates a mutable borrow. reference has type &mut i32.
  3. *reference += 1; dereferences reference, reaches total, and changes it from 5 to 6.
  4. let next = *reference + 1; reads the integer behind the reference (6), then adds 1. next becomes 7.
  5. println!("{next}"); prints 7.

The dereference in steps 3 and 4 accesses the same original value. No second integer is created by &mut total; the reference only borrows access to total.

For a non-Copy value, dereferencing does not automatically permit moving the value out of a reference:

let name = String::from("Ada");
let name_ref = &name;

// let moved = *name_ref; // error: cannot move a String out of a shared reference
println!("{name_ref}");

This restriction protects ownership. You can borrow the string, use methods on it, or clone it when an independent owned value is needed.

Real World Use Cases

Updating a counter

A function can update a caller's variable without returning a replacement value:

fn record_visit(visits: &mut u32) {
    *visits += 1;
}

Reading configuration without copying it

Configuration structures often contain strings, vectors, and nested data. Passing &Config avoids moving or cloning the configuration:

struct Config {
    port: u16,
}

fn start_server(config: &Config) {
    println!("Starting on port {}", config.port);
}

Editing a collection in place

fn uppercase_first_name(names: &mut Vec<String>) {
    if let Some(first) = names.first_mut() {
        *first = first.to_uppercase();
    }
}

first_mut() returns a mutable reference to an element. Dereferencing on the left side of = replaces that element.

Parsing and data processing

References allow functions to inspect a large input without allocating copies:

fn count_commas(text: &str) -> usize {
    text.chars().filter(|ch| *ch == ',').count()
}

Here, text is a borrowed string slice (&str).

Take Quiz

Real Codebase Usage

Rust code usually uses references in function parameters to make ownership requirements explicit.

Prefer immutable borrows for read-only work

fn is_valid_email(email: &str) -> bool {
    email.contains('@')
}

Using &str communicates that validation only reads the input.

Use mutable borrows for in-place updates

fn normalize_name(name: &mut String) {
    *name = name.trim().to_lowercase();
}

The signature makes mutation visible at the call site:

let mut name = String::from("  ADA  ");
normalize_name(&mut name);

Use guard clauses with borrowed data

fn first_active_user_name(users: &[String]) -> Option<&String> {
    if users.is_empty() {
        return None;
    }

    users.first()
}

The returned reference is tied to the input slice. Rust ensures it cannot outlive users.

Rely on method-call deref coercion when appropriate

Method calls are ergonomic because Rust can automatically borrow and dereference receivers in many cases:

let message = String::from("hello");
let length = message.len();

You do not write (*message).len(). Still, with operators and generic trait APIs, explicit * may be clearer or necessary.

Keep mutable borrows short

let mut balance = 100;

{
    let balance_ref = &mut balance;
    *balance_ref -= 20;
}

println!("Remaining: {balance}");

The inner block makes the mutable borrow end before balance is used again. Modern Rust can often end borrows earlier automatically, but small scopes can make intent obvious.

Common Mistakes

Assuming every reference is automatically dereferenced

This is not a general Rust rule:

let mut value = 5;
let reference = &mut value;

// let result = reference + 1; // error: &mut i32 does not implement this addition
let result = *reference + 1;

Use *reference when an expression needs the value behind the reference. Remember that some operations may happen to work for particular reference types because of trait implementations.

Trying to mutate through &T

let value = 5;
let reference = &value;

// *reference += 1; // error: immutable reference cannot modify the value

To mutate, both the owner and the borrow must be mutable:

let mut value = 5;
let reference = &mut value;
*reference += 1;

Forgetting mut on the binding that owns the value

let value = 5;
// let reference = &mut value; // error: value is not mutable

Write let mut value = 5; before taking &mut value.

Treating &mut T as “an immutable reference to a mutable value”

&mut T is not just &T with extra permission. It is a separate type with exclusive-access rules. Rust can often coerce &mut T to &T in function arguments, but trait/operator selection does not automatically make all &T operator implementations apply to &mut T.

Moving a non-Copy value out of a reference

let title = String::from("Rust");
let title_ref = &title;

// let owned_title = *title_ref; // error: cannot move out of borrowed content

Use the borrowed value directly, call methods that borrow it, or create an explicit clone:

let owned_title = title_ref.clone();

Confusing * with multiplication

The same symbol has different meanings depending on position:

let product = 3 * 4; // multiplication

let value = 4;
let reference = &value;
let read = *reference; // dereference

A prefix * dereferences; an infix * multiplies.

Comparisons

FormMeaningCan read?Can modify the referred value?
TOwned valueYesIf the binding is mut
&TImmutable reference (shared borrow)YesNo
&mut TMutable reference (exclusive borrow)YesYes
*referenceAccess the value/place behind a referenceYesOnly when the reference is &mut T

&T versus &mut T

let mut number = 10;

let read_only = &number;
println!("{read_only}");

let editable = &mut number;
*editable += 1;

The immutable borrow must no longer be in use before taking the mutable borrow.

Explicit dereferencing versus deref coercion

SituationIs automatic dereferencing common?Example
Function argument expecting a related reference typeYes, through deref coercionPassing &String where &str is expected
Method callOftenmy_string.len()
Binary operatorDepends on trait implementations&i32 + 1 happens to work; &mut i32 + 1 does not
Assignment through a mutable referenceNo; use **value_ref = 10

Rust references versus C++ references

Rust references may look familiar to C++ developers, but Rust makes borrowing permissions and lifetimes part of the type system.

  • Rust uses &T and &mut T to distinguish shared and exclusive access.
  • Rust references are checked by the borrow checker.
  • Rust's * explicitly dereferences a reference, though method calls and coercions provide ergonomics in specific contexts.
  • Operator behavior in Rust is trait-driven, not a universal implicit dereference rule.
Take Quiz

Cheat Sheet

let x = 5;
let r = &x;      // r: &i32, immutable borrow
let y = *r;      // dereference; y: i32

let mut x = 5;
let r = &mut x;  // r: &mut i32, mutable borrow
*r += 1;         // modify x through r
  • &expr: borrow expr immutably.
  • &mut expr: borrow expr mutably.
  • *ref: dereference; access the referred value or place.
  • &T allows reading but not mutation.
  • &mut T allows reading and mutation, with exclusive access.
  • &T and &mut T are distinct types.
  • &i32 + 1 works because the standard library provides an applicable Add implementation.
  • &mut i32 + 1 does not work because it lacks that applicable implementation.
  • Use *rc + 1 when you need the underlying number.
  • * can mean multiplication (a * b) or dereferencing (*reference) depending on its position.
  • Dereferencing a non-Copy value does not let you move it out of a reference.

FAQ

What does & mean in Rust?

& creates an immutable reference, also called an immutable borrow. It lets code read a value without taking ownership of it.

What does &mut mean in Rust?

&mut creates a mutable reference. It grants temporary exclusive access, allowing the referenced value to be changed.

What does * mean in Rust?

Before a reference, * dereferences it: it accesses the value behind the reference. Between two numeric expressions, * means multiplication.

Does Rust automatically dereference references?

Not everywhere. Rust performs deref coercions in selected contexts, especially function calls and method calls. Operators use trait implementations, so whether an operator works on a reference depends on the available implementation.

Why does &i32 + 1 compile?

The standard library provides addition implementations for references to primitive numeric types, including &i32 in this situation. This is operator support provided by traits, not universal automatic dereferencing.

Why does &mut i32 + 1 fail?

&mut i32 is a distinct type, and there is no applicable standard-library Add<i32> implementation for it. Dereference it first: *reference + 1.

Can I read through a mutable reference?

Yes. &mut T permits both reading and writing. For example, let copy = *mutable_reference; works when reading is allowed by the value's ownership rules.

Why can't I move a String with *reference?

A shared reference does not give ownership. String is not Copy, so moving it out through &String would invalidate the original owner. Borrow it, or explicitly clone it if an owned copy is needed.

Take Quiz

Mini Project

Description

Build a small order-total updater that borrows an order amount, calculates a value through immutable references, and updates the amount through a mutable reference. This demonstrates the difference between reading and modifying through Rust references.

Goal

Calculate a discounted total and then apply a fee by mutating the original total through &mut f64.

Requirements

  1. Create a mutable order total as an f64.
  2. Write a function that reads a total through an immutable reference and returns a discounted amount.
  3. Write a function that accepts a mutable reference and adds a fee to the total.
  4. Use explicit dereferencing where the numeric calculation or assignment needs the underlying value.
  5. Print both the discounted amount and the final updated total.
Take Quiz

Keep learning

Related questions

View more