iosNudge

What do the mutating and inout keywords do?

EasySwift 2 min read

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 let

A 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)   // 10

Rules:

  • The argument must be a var (not a let or 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 better

Common follow-up questions

  • Is inout the 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.append need var? append is a mutating method, and you cannot mutate a let.
  • What is a nonmutating setter? A setter on a value type that does not change self, for example when it writes to a class or external storage. SwiftUI's @State uses this.
  • Can a mutating method be called on a struct property of a class? Yes, if the class instance is reachable, even through a let reference, because the property itself is a var.