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.
&valuecreates an immutable reference, with type&T.&mut valuecreates a mutable reference, with type&mut T.*referencedereferences 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:
- You can have any number of immutable references (
&T). - 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.
&valuegives someone a read-only visitor pass to the box.&mut valuegives someone the only edit pass to the box.*referencemeans: “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 *.
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}");
}
let mut total = 5;creates an owned, mutablei32namedtotal.let reference = &mut total;creates a mutable borrow.referencehas type&mut i32.*reference += 1;dereferencesreference, reachestotal, and changes it from5to6.let next = *reference + 1;reads the integer behind the reference (6), then adds1.nextbecomes7.println!("{next}");prints7.
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).
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
| Form | Meaning | Can read? | Can modify the referred value? |
|---|---|---|---|
T | Owned value | Yes | If the binding is mut |
&T | Immutable reference (shared borrow) | Yes | No |
&mut T | Mutable reference (exclusive borrow) | Yes | Yes |
*reference | Access the value/place behind a reference | Yes | Only 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
| Situation | Is automatic dereferencing common? | Example |
|---|---|---|
| Function argument expecting a related reference type | Yes, through deref coercion | Passing &String where &str is expected |
| Method call | Often | my_string.len() |
| Binary operator | Depends on trait implementations | &i32 + 1 happens to work; &mut i32 + 1 does not |
| Assignment through a mutable reference | No; 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
&Tand&mut Tto 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.
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: borrowexprimmutably.&mut expr: borrowexprmutably.*ref: dereference; access the referred value or place.&Tallows reading but not mutation.&mut Tallows reading and mutation, with exclusive access.&Tand&mut Tare distinct types.&i32 + 1works because the standard library provides an applicableAddimplementation.&mut i32 + 1does not work because it lacks that applicable implementation.- Use
*rc + 1when you need the underlying number. *can mean multiplication (a * b) or dereferencing (*reference) depending on its position.- Dereferencing a non-
Copyvalue 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.
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
- Create a mutable order total as an
f64. - Write a function that reads a total through an immutable reference and returns a discounted amount.
- Write a function that accepts a mutable reference and adds a fee to the total.
- Use explicit dereferencing where the numeric calculation or assignment needs the underlying value.
- Print both the discounted amount and the final updated total.
Keep learning
Related questions
Absolute Values in Rust: Using the abs() Method
Learn how to calculate absolute values in modern Rust with abs(), handle signed integer overflow, and choose safer alternatives.
Accepting Iterators in Rust Functions with impl Iterator
Learn how to write Rust functions that accept HashMap iterators using generic bounds or impl Iterator, avoiding trait object and lifetime errors.
Access Enum Variant Values in Rust with Pattern Matching
Learn how to extract and print values stored in Rust enum variants using match and if let pattern matching, with practical examples.