Rust Mutability and References: mut T, &T, &mut T, and mut &T
Rust makes mutability explicit. This can feel strange when coming from Python or JavaScript, where variables and references are usually easier to reassign without much syntax.
A useful way to understand Rust is to separate two questions:
- Do I own the value, or am I borrowing it?
- Do I want to change the variable, the value, or neither?
That gives us four common patterns:
mut T&T&mut Tmut &T
They look similar, but they mean different things.
mut T: I Own This Value and Want to Change It
Use mut T when you have a normal owned value and want to change it.
let mut score = 10;
score = 20;
score += 5;
println!("{score}");Here, score is not a reference. It is the actual value. The mut means the variable can change.
You can also use it inside functions:
fn add_bonus(mut score: i32) -> i32 {
score += 10;
score
}The function receives its own copy of the score, changes it locally, and returns the new value.
Use mut T when you want local state that you control.
&T: I Only Want to Read This Value
Use &T when you want to borrow a value without changing it.
fn print_name(name: &String) {
println!("Hello, {name}");
}This function does not own the String. It only borrows it.
let name = String::from("Andres");
print_name(&name);
println!("{name}");Because print_name only borrowed name, the original variable can still be used after the function call.
Use &T when a function only needs to read something.
&mut T: I Want to Borrow This Value and Change It
Use &mut T when you want a function to modify a value without taking ownership of it.
fn add_exclamation(message: &mut String) {
message.push('!');
}Usage:
let mut greeting = String::from("hello");
add_exclamation(&mut greeting);
println!("{greeting}");The function receives mutable access to the original String. It does not create a new one. It changes the existing value.
Rust requires this mutable borrow to be exclusive. While something has &mut T, Rust does not allow other active references to the same value.
This prevents bugs where two parts of the code try to read and write the same data at the same time.
Use &mut T when a function should update an existing value.
mut &T: I Want to Change Which Value I Am Looking At
This one is easy to misunderstand.
mut &T does not mean you can change the value being referenced.
It means the reference variable itself can point somewhere else.
let first = String::from("Alice");
let second = String::from("Bob");
let mut current: &String = &first;
println!("{current}");
current = &second;
println!("{current}");Here, current first points to first, then points to second.
But current cannot modify either string.
// current.push('!'); // not allowedSo mut &T means:
I can change which value I am looking at, but I cannot change the value through this reference.
Use this when you need a “current selected item” or “current best item” while only reading data.
fn longest_name<'a>(names: &'a [String]) -> &'a String {
let mut longest: &String = &names[0];
for name in &names[1..] {
if name.len() > longest.len() {
longest = name;
}
}
longest
}The function never changes the names. It only changes which name longest points to.
Small Comparison
let mut age = 30; // mut T
age += 1;
let name = String::from("Ana");
let read_only_name = &name; // &T
let mut message = String::from("hello");
let editable_message = &mut message; // &mut T
editable_message.push('!');
let first = String::from("red");
let second = String::from("blue");
let mut selected: &String = &first; // mut &T
selected = &second;The practical difference is:
mut T: change your own value&T: read someone else’s value&mut T: change someone else’s value with permissionmut &T: change which value you are looking at
Mapping to Other Languages
mut T
Rust:
let mut x = 1;
x = 2;JavaScript:
let x = 1;
x = 2;Python:
x = 1
x = 2C:
int x = 1;
x = 2;This is the simplest case: a normal variable that can change.
&T
Rust:
fn print_value(value: &i32) {
println!("{value}");
}C closest equivalent:
const int* value;In JavaScript and Python, references exist, but the language does not have the same built-in read-only borrowing rule.
&mut T
Rust:
fn increase(value: &mut i32) {
*value += 1;
}C closest equivalent:
int* value;But Rust adds safety rules. It makes sure only one mutable reference exists at a time.
JavaScript and Python allow shared mutation more freely, but they do not protect you from aliasing bugs in the same way.
mut &T
Rust:
let mut selected = &first;
selected = &second;JavaScript:
let selected = first;
selected = second;Python:
selected = first
selected = secondC closest equivalent:
const int* selected = &first;
selected = &second;The variable can point somewhere else, but the value is still read-only through that handle.
TL;DR: Purpose-Driven Cases
Use the syntax based on what you are trying to do.
I have a value and I want to change it
Use mut T.
let mut count = 0;
count += 1;Good for:
- counters
- totals
- local variables
- temporary values
- values you own
I want to pass a value to a function without giving it away
Use &T.
fn print_count(count: &i32) {
println!("{count}");
}Good for:
- printing
- checking
- reading
- validation
- functions that should not modify data
I want a function to update an existing value
Use &mut T.
fn reset_count(count: &mut i32) {
*count = 0;
}Good for:
- modifying a string
- updating a list
- changing a struct
- resetting state
- in-place changes
I want to switch between values, but only read them
Use mut &T.
let mut current = &first;
current = &second;Good for:
- selected item
- current item
- longest item
- best match
- cursor-like logic over read-only data
Conclusion
Rust becomes much easier when you separate two ideas:
- Can the variable itself change?
- Can the value behind the variable change?
The short version is:
mut T: I own it, and I want to change it.&T: I only want to read it.&mut T: I want to borrow it and change it.mut &T: I want to change which value I am reading.
Rust is not making mutability complicated for no reason. It is making ownership, borrowing, and mutation visible so the compiler can prevent common bugs.