Question
How can I mutate a field of a Rust struct from one of its methods?
For example, I want up to increase a point's y coordinate:
struct Point {
x: i32,
y: i32,
}
impl Point {
fn up(&self) {
self.y += 1;
}
}
fn main() {
let p = Point { x: 0, y: 0 };
p.up();
}
However, Rust reports that self.y cannot be assigned through an immutable reference. What changes are required to mutate the field correctly?
Short Answer
You will learn how Rust controls mutation through ownership and borrowing. In particular, you will see why a method that changes a struct needs &mut self, why the caller's variable must be declared with mut, and how to call mutating methods safely.
Concept
Rust distinguishes between reading a value and changing it.
In a method, the self parameter determines how that method accesses its instance:
selftakes ownership of the value.&selfimmutably borrows the value. The method may read fields but may not modify them.&mut selfmutably borrows the value. The method may read and modify fields.
Your up method changes y, so it requires a mutable borrow:
fn up(&mut self) {
self.y += 1;
}
The caller must also allow the Point to be mutated by declaring its binding with mut:
let mut p = Point { x: 0, y: 0 };
p.up();
These rules are part of Rust's ownership system. They prevent unexpected changes through read-only references and help Rust guarantee memory safety without a garbage collector.
Mental Model
Think of a struct value as a notebook.
&selfis borrowing the notebook with read-only permission. You can inspect the coordinates, but you cannot erase or write anything.&mut selfis borrowing the notebook with editing permission. You can update a coordinate.let mut pmeans the notebook is allowed to be edited at all.
Rust requires both permissions: the method must request permission to edit, and the variable owner must grant it.
Syntax and Examples
A method that changes its struct uses &mut self.
struct Point {
x: i32,
y: i32,
}
impl Point {
fn up(&mut self) {
self.y += 1;
}
}
fn main() {
let mut p = Point { x: 0, y: 0 };
p.up();
println!("({}, {})", p.x, p.y);
}
Output:
(0, 1)
p.up() automatically passes a mutable reference, effectively behaving like this:
Point::up(&mut p);
Rust performs this automatic borrowing only when the variable can legally be mutably borrowed.
Step by Step Execution
Consider this program:
struct Point {
x: i32,
y: i32,
}
impl Point {
fn up(&mut self) {
self.y += 1;
}
}
fn main() {
let mut p = Point { x: 3, y: 5 };
p.up();
println!("{}", p.y);
}
Execution steps:
let mut pcreates a mutable binding namedpcontainingPoint { x: 3, y: 5 }.p.up()calls theupmethod.- Because
upexpects&mut self, Rust temporarily mutably borrowspas&mut p. - Inside the method,
selfrefers to that mutable borrow. self.y += 1changesyfrom5to6.- The mutable borrow ends when the method returns.
println!readsp.yand prints6.
While the mutable borrow is active, Rust does not allow conflicting borrows of the same value.
Real World Use Cases
Mutable methods are common when a type represents state that changes over time.
- Game movement: update a player's position, velocity, health, or score.
- Counters and metrics: increment a request count or retry count.
- Shopping carts: add or remove items from a cart struct.
- Parsers: advance a cursor through input text.
- Network clients: update connection state, buffers, or cached data.
- Data processing: append results to a collection held by a struct.
Example: incrementing a retry counter.
struct Job {
retries: u32,
}
impl Job {
fn record_retry(&mut self) {
self.retries += 1;
}
}
Real Codebase Usage
In real Rust codebases, methods are usually designed around whether they observe, mutate, or consume a value.
Read-only methods use &self
impl Point {
fn distance_from_origin(&self) -> f64 {
let squared = self.x * self.x + self.y * self.y;
(squared as f64).sqrt()
}
}
State-changing methods use &mut self
impl Point {
fn move_by(&mut self, dx: i32, dy: i32) {
self.x += dx;
self.y += dy;
}
}
Methods that consume and replace a value can use self
impl Point {
fn moved_by(self, dx: i32, dy: i32) -> Self {
Self {
x: self.x + dx,
y: self.y + dy,
}
}
}
A common design pattern is to provide both forms when useful:
move_by(&mut self, ...)changes the existing value.moved_by(self, ...) -> Selfproduces an updated value instead.
This makes mutation explicit at the call site and lets callers choose the style that fits their code.
Common Mistakes
Using &self when changing a field
This is invalid because &self is an immutable reference:
impl Point {
fn up(&self) {
self.y += 1;
}
}
Fix it by requesting a mutable reference:
fn up(&mut self) {
self.y += 1;
}
Forgetting mut on the local variable
This also fails:
let p = Point { x: 0, y: 0 };
p.up();
Even if up takes &mut self, p itself must be mutable:
let mut p = Point { x: 0, y: 0 };
p.up();
Assuming mut applies to only one field
Rust applies mutability to the binding, not to individual ordinary struct fields. With a mutable Point, both x and y may be changed:
let mut p = Point { x: 0, y: 0 };
p.x = 10;
p.y = 20;
If a design needs carefully controlled mutation, keep fields private and expose only appropriate methods.
Holding an immutable borrow while trying to mutate
let mut p = Point { x: 0, y: 0 };
let y_ref = &p.y;
p.up();
println!("{y_ref}");
This is rejected because y_ref still reads from p when p.up() needs exclusive mutable access. Use the read before mutation, or make the immutable borrow end before calling the mutating method.
Using self unnecessarily
This compiles, but moves the point into the method:
fn up(mut self) {
self.y += 1;
}
The original caller cannot use p afterward unless the method returns the updated value. For in-place updates, &mut self is generally the intended choice.
Comparisons
| Receiver | What the method receives | Can it mutate fields? | Can the caller use the original value afterward? |
|---|---|---|---|
&self | Immutable reference | No | Yes |
&mut self | Mutable reference | Yes | Yes, after the borrow ends |
self | Ownership of the value | Yes, if declared mut self | No, unless the value is returned |
Example of each receiver:
impl Point {
fn y(&self) -> i32 {
self.y
}
fn up(&mut self) {
self.y += 1;
}
fn into_tuple(self) -> (i32, i32) {
(self.x, self.y)
}
}
&self is for inspection, &mut self is for in-place updates, and self is for operations that take over or transform the value.
Cheat Sheet
struct Point {
x: i32,
y: i32,
}
impl Point {
// Read-only access
fn get_y(&self) -> i32 {
self.y
}
// In-place mutation
fn up(&mut self) {
self.y += 1;
}
}
fn main() {
let mut p = Point { x: 0, y: 0 };
p.up(); // Works: p is mutable
println!("{}", p.get_y());
}
Quick rules:
- Use
&selfto read a struct. - Use
&mut selfto change a struct. - Declare the caller's binding as
let mut valuebefore calling an&mut selfmethod. - Rust allows either many immutable borrows or one mutable borrow at a time for the same value.
- Use
selfwhen the method should consume the instance.
FAQ
Why do I need both &mut self and let mut p?
&mut self declares that the method needs write access. let mut p declares that the caller permits p to be mutably borrowed. Rust requires both sides to agree.
Can a method with &self ever change a struct?
Not directly through normal fields. Rust provides specialized interior-mutability types such as Cell and RefCell, but ordinary mutation should use &mut self.
Does &mut self move the struct?
No. It temporarily borrows the struct mutably. The caller still owns the struct after the method finishes.
Can I change just one field without making the entire variable mutable?
Not for normal struct fields. Rust's normal mutability is attached to the variable binding. Use methods and private fields to control which updates are allowed.
Why can only one mutable borrow exist at a time?
Exclusive mutable access prevents data races and invalid references. This rule lets Rust catch many bugs during compilation.
Should I use self or &mut self for an update method?
Use &mut self when you want to update the existing instance. Use self when the method consumes the old value, often returning a new one.
Can a method return &mut Self?
Yes. This is useful for chaining mutating calls:
fn up(&mut self) -> &mut Self {
self.y += 1;
self
}
Mini Project
Description
Build a small position tracker for an object on a two-dimensional grid. The tracker demonstrates read-only methods, mutating methods, and a method that resets state without exposing direct field changes throughout the program.
Goal
Create a mutable Position value, move it in several directions, reset it, and print its coordinates after each operation.
Requirements
Create a Position struct with x and y fields.
Add mutating methods to move up, down, left, and right.
Add a read-only method that returns the coordinates as a tuple.
Add a mutating reset method that returns the position to (0, 0).
Declare the position variable as mutable and print its coordinates after updates.
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.