What do the mutating and inout keywords do?
TL;DR
mutating marks a method on a struct or enum that changes self. You cannot call it on a let value.
inout marks a function parameter that the function can change. The caller passes it with &.
Both work with copy-in, copy-out: Swift gives you a temporary value, then writes it back at the end.
mutating
Value types are immutable inside their methods by default.
struct Counter {
var value = 0
func bad() {
// value += 1 // error: self is immutable
}
mutating func increment() {
value += 1 // OK
}
}
var c = Counter()
c.increment() // OK, c is a var
let fixed = Counter()
// fixed.increment() // error: fixed is a letA mutating method can even replace self:
enum Light {
case on, off
mutating func toggle() {
self = (self == .on) ? .off : .on
}
}Classes do not need mutating. self is a reference, and you change the object it points to.
mutating in protocols
protocol Resettable {
mutating func reset()
}
struct Score: Resettable {
var points = 10
mutating func reset() { points = 0 }
}
final class Game: Resettable {
var level = 3
func reset() { level = 1 } // no `mutating` for classes
}Mark protocol requirements mutating if structs should be able to change self. Classes simply ignore the keyword.
inout
A normal parameter is a constant copy. inout lets you change the caller's variable.
func double(_ number: inout Int) {
number *= 2
}
var x = 5
double(&x)
print(x) // 10Rules:
- The argument must be a
var(not aletor a literal). - You must write
&at the call site. This makes the change visible to readers.
func swapValues<T>(_ a: inout T, _ b: inout T) {
(a, b) = (b, a)
}
var first = "A", second = "B"
swapValues(&first, &second)The standard library already has swap(_:_:).
How it works: copy-in, copy-out
Swift copies the value in, lets the function change it, then writes it back when the function returns. The optimizer often passes a direct reference instead, but the behavior is the same.
This matters for property observers:
struct Settings {
var volume = 5 {
didSet { print("volume changed to \(volume)") }
}
}
func bump(_ v: inout Int) { v += 1; v += 1 }
var s = Settings()
bump(&s.volume) // prints once: "volume changed to 7"didSet runs once, at write-back, not on every change inside the function.
Exclusive access
Swift does not allow two overlapping accesses to the same variable when one is a write.
var n = 1
func add(_ a: inout Int, _ b: inout Int) { a += b }
// add(&n, &n) // error: overlapping accesses to 'n'This is the law of exclusivity. It prevents confusing bugs and helps the optimizer. Some cases are caught at compile time, others at run time.
inout and closures
An escaping closure cannot capture an inout parameter. The parameter only lives until the function returns.
func bad(_ value: inout Int) {
// DispatchQueue.main.async { value += 1 } // error
}When to use inout
- Helper functions that update a large value in place, like
func applyDiscount(_ cart: inout Cart). - Avoid it for normal code where returning a new value is clearer:
func doubled(_ n: Int) -> Int { n * 2 } // often betterCommon follow-up questions
- Is
inoutthe same as passing by reference? Not exactly. The rule is copy-in, copy-out. The compiler may use a reference as an optimization. - Can you pass a computed property as
inout? Yes. Swift calls the getter, runs the function, then calls the setter. - Why does
Array.appendneedvar?appendis amutatingmethod, and you cannot mutate alet. - What is a
nonmutatingsetter? A setter on a value type that does not changeself, for example when it writes to a class or external storage. SwiftUI's@Stateuses this. - Can a
mutatingmethod be called on a struct property of a class? Yes, if the class instance is reachable, even through aletreference, because the property itself is avar.