Swift mutating keyword exists because value types are immutable by default — a design choice that prevents accidental side effects but sometimes gets in the way when you need a struct method to modify its own properties.
My first reaction was: it’s my own property, why can’t I change it?
This problem sits right at the heart of Swift’s distinction between value types and reference types. The mutating keyword exists specifically to solve this problem. In this article I’ll walk through the compiler’s logic behind mutating, show the exact code that triggers the error and how to fix it, and share a state machine pattern from a production app that relies on mutating for every state transition.
The Error: Why Can’t a Struct Method Change Its Own Property?
Take a look at this code — it won’t compile:
Code 1: Without mutating — compile error
struct Player {
var name: String
var score: Int
// ❌ This does NOT compile
func addToScore(points: Int) {
score += points // ERROR: Cannot assign to property: 'self' is immutable
}
}
The compiler rejects this immediately. But if you change struct to class, the same code compiles fine. Why?
Because classes are reference types — instance methods can modify the instance’s properties by default. You’re modifying data on the same object in the heap. Structs, however, are value types. When you call a method, the compiler assumes you’re operating on an immutable copy, so modifications aren’t allowed.
Here’s proof of why this logic makes sense:
var alice = Player(name: "Alice", score: 100)
var aliceCopy = alice // value type: copy created
// If struct methods could freely modify properties,
// aliceCopy.score would ALSO change — that breaks the "each instance is independent" guarantee.
// That's why the compiler enforces the mutating rule.
Code 2: With mutating — compiles and works
struct Player {
var name: String
var score: Int
// ✅ Add mutating — now it compiles
mutating func addToScore(points: Int) {
score += points
}
}
var player = Player(name: "Alice", score: 100)
player.addToScore(points: 50)
print(player.score) // 150 — modification worked!
What Does mutating Actually Do Under the Hood?
A mutating method does three things behind the scenes:
1. Turns self from an immutable value into a mutable one
2. Modifies the properties
3. Writes the modified value back to the original variable
You can think of a mutating method as an implicit inout parameter:
Code 3: mutating is essentially “implicit inout self”
struct Player {
var name: String
var score: Int
// This mutating method:
mutating func levelUp() {
score += 100
name += " ⭐"
}
}
// What the compiler effectively generates:
// func levelUp(inout self: Player) {
// self.score += 100
// self.name += " ⭐"
// }
var player = Player(name: "Alice", score: 0)
player.levelUp()
print(player) // Player(name: "Alice ⭐", score: 100)
Because of this, mutating can modify properties and also replace the entire self:
struct Color {
var r: Double, g: Double, b: Double
// mutating can replace self entirely — return a new instance
mutating func invert() {
self = Color(r: 1 - r, g: 1 - g, b: 1 - b)
}
mutating func desaturate() {
let gray = 0.299 * r + 0.587 * g + 0.114 * b
self = Color(r: gray, g: gray, b: gray)
}
}
var red = Color(r: 1, g: 0, b: 0)
red.invert()
print(red) // Color(r: 0.0, g: 1.0, b: 1.0) — cyan!
var blue = Color(r: 0, g: 0, b: 1)
blue.desaturate()
print(blue) // Color(r: 0.114, g: 0.114, b: 0.114) — gray
let Structs Can’t Call mutating Methods
Here’s a trap that’s easy to fall into:
let fixedPlayer = Player(name: "Bob", score: 200)
fixedPlayer.addToScore(points: 10)
// ❌ ERROR: Cannot use mutating member on immutable value: 'fixedPlayer' is a 'let' constant
// Because let declares an immutable variable, none of its properties can be modified
// — and mutating methods are fundamentally modifications. So they're prohibited.
So:
• var struct → can call mutating methods
• let struct → cannot call mutating methods
• Non-mutating methods on a struct → can be called on both let and var, but can’t modify properties
Why Don’t Classes Need mutating?
Code 4: Value type vs reference type — the memory difference
// CLASS: reference type — self is a POINTER to heap data
class ClassPlayer {
var name: String
var score: Int
init(name: String, score: Int) {
self.name = name
self.score = score
}
// No mutating needed — self is a pointer, modifying the heap object
func addToScore(points: Int) {
score += points // modifies heap data through pointer
}
}
// STRUCT: value type — self IS the data (on stack)
struct StructPlayer {
var name: String
var score: Int
// mutating needed — self must be replaced, not just pointed to
mutating func addToScore(points: Int) {
score += points // replaces self with modified copy
}
}
// Proof: class shares data, struct copies it
let classA = ClassPlayer(name: "Alice", score: 100)
let classB = classA // both point to SAME heap object
classB.addToScore(points: 50)
print(classA.score) // 150 — classA is also affected!
var structA = StructPlayer(name: "Alice", score: 100)
var structB = structA // structB is a COPY
structB.addToScore(points: 50)
print(structA.score) // 100 — structA is UNCHANGED ✅
Class instances are stored on the heap, and variables hold a pointer to the heap memory. When a method is called, self is a pointer to the heap object. Modifying self.name is “modifying data on the heap through the pointer” — it doesn’t involve changing the pointer itself, so no special marking is needed.
Structs, however, store their actual data on the stack (mostly). When a method is called, self is the value itself. To modify it, you need to replace the whole value — hence the explicit mutating declaration.
Protocol Requirements: mutating in Protocols
When a protocol declares a method as mutating, conforming types must also mark their implementation as mutating (if they’re value types).
Code 5: Protocol with mutating requirement
// Protocol defines the mutating requirement
protocol Resettable {
mutating func reset()
}
struct GameState: Resettable {
var score: Int
var level: Int
// Struct MUST use mutating to satisfy the protocol requirement
mutating func reset() {
score = 0
level = 1
}
}
class Session: Resettable {
var isActive: Bool = true
// Class does NOT need mutating — it satisfies the requirement implicitly
func reset() {
isActive = false
}
}
// Usage
var game = GameState(score: 500, level: 10)
game.reset()
print(game) // GameState(score: 0, level: 1)
let session = Session()
session.reset()
print(session.isActive) // false
This is important to understand: a class can satisfy a mutating protocol requirement without marking its method as mutating. This is because class methods can always modify properties — the compiler doesn’t need extra permission.
A Real-World Scenario: Structs as a State Machine
Here’s a pattern I’ve actually used in a production app — a simple state machine implemented as a struct, with mutating methods for state transitions.
Code 6: State machine using mutating
enum State {
case idle, loading, loaded([String]), error(String)
}
struct DataLoader {
private(set) var state: State = .idle
mutating func fetch(url: String) {
state = .loading
// In a real app, this would be an async call.
// The mutating method captures the state transition.
}
mutating func succeed(data: [String]) {
state = .loaded(data)
}
mutating func fail(message: String) {
state = .error(message)
}
mutating func retry() {
if case .error = state {
state = .idle
}
}
}
// Usage — each state change is a new value
var loader = DataLoader()
print(loader.state) // idle
loader.fetch(url: "/api/data")
print(loader.state) // loading
loader.succeed(data: ["item1", "item2"])
print(loader.state) // loaded(["item1", "item2"])
loader.fail(message: "network error")
print(loader.state) // error("network error")
loader.retry()
print(loader.state) // idle
// Testing is easy — each state is independent and reproducible
func testRetry() {
var loader = DataLoader()
loader.fail(message: "timeout")
loader.retry()
assert(loader.state == .idle) // ✅ passes
}
The advantage of this design: all state changes are encapsulated inside the struct. External code can only change state through defined methods, and each state change produces a new value (via the implicit self replacement in mutating methods). This makes testing easy — each state is independent and reproducible.
Can You Use mutating on Computed Properties?
No. A computed property getter can’t be mutating. However, if you want to modify other properties inside its setter, the setter is implicitly treated as mutating:
struct Thermometer {
var celsius: Double
// Computed property — the setter is implicitly mutating
var fahrenheit: Double {
get { return celsius * 9 / 5 + 32 }
set { celsius = (newValue - 32) * 5 / 9 } // modifies celsius
}
}
var t = Thermometer(celsius: 100)
t.fahrenheit = 212 // triggers the setter
print(t.celsius) // 100.0 — celsius was modified through the computed property
Summary
| Feature | Struct (Value Type) | Class (Reference Type) |
|---|---|---|
| Storage location | Stack (mostly) | Heap |
| Modifying own properties | Requires mutating | Not required |
let variable can modify properties? | Cannot call mutating methods | Can (let only fixes the reference, the object is mutable) |
| Protocol mutating requirement | Must use mutating in implementation | Satisfies implicitly (no mutating needed) |
Three key takeaways:
• mutating is a “permit” for value types to modify themselves — without it, struct/enum methods can only read, not change.
• mutating is essentially “implicit inout self” — the method receives the address of self, modifies it, and writes it back.
• Structs declared with let can’t call mutating methods, even if they exist — the “immutable variable” semantics take priority over the method declaration.
My final advice: think of mutating as a form of “self-awareness” inside a struct — it tells the compiler “this method will change me, please allow it.” And let declarations tell the compiler from outside “this variable can’t be changed, no matter what.” Together, they form Swift’s complete safety net for value types.
In real projects, when you find yourself frequently modifying a struct’s properties, consider: should this actually be a class? Or should some parts be extracted out? mutating gives you flexibility, but “frequent modification” might hint at a design choice — value types are better suited for “created once, read many times” scenarios.
