Memory Safety

By default Swift prevents unsafe behavior from happening in your code. It won't let you read uninitialized memory, it bounds-checks arrays, and it guarantees that memory is initialized before use. Among these guarantees, Swift also ensures that multiple accesses to the same region of memory don't conflict. This chapter explains what a conflicting access is and how to avoid one.

Understanding conflicting access to memory

Access to memory happens whenever you do things like set the value of a variable or pass an argument to a function. For example, this code performs both a write access and a read access:

var one = 1            // a write access to one
print("We're number \(one)!")   // a read access to one

Most accesses are instantaneous and never overlap.

A conflicting access can occur when different parts of your code try to access the same location in memory at the same time. Multiple accesses to a location at the same time can produce unpredictable or inconsistent behavior. In Swift, there are ways to modify a value that span several lines of code, making it possible to attempt to access a value in the middle of its own modification.

Note: if you've written concurrent or multithreaded code, conflicting access to memory might be a familiar problem. However, the conflicting access discussed here can happen on a single thread and doesn't involve concurrency. If a conflict happens across threads or tasks, Swift 6's strict concurrency checking and the Sendable rules catch those at compile time instead.

Characteristics of memory access

There are three characteristics of a memory access to consider in the context of conflicting access: whether the access is a read or a write, the duration of the access, and the location in memory being accessed. Specifically, a conflict occurs if you have two accesses that meet all of the following conditions:

The difference between a read and a write access is usually obvious: a write changes the location in memory, but a read doesn't. The location refers to what's being accessed — a variable, constant, or property.

The duration of a memory access is either instantaneous or long-term. An access is instantaneous if no other code can run after that access starts but before it ends. By their nature, two instantaneous accesses can't happen at the same time. Most accesses are instantaneous:

func oneMore(than number: Int) -> Int {
    return number + 1
}

var myNumber = 1
myNumber = oneMore(than: myNumber)   // instantaneous read then write
print(myNumber)                       // → 2

Instantaneous accesses don't overlap, so there's no conflict.

However, several kinds of access — called long-term accesses — span the execution of other code. The difference is that other code can run after a long-term access starts but before it ends, which is called overlap. A long-term access can overlap with other long-term accesses and with instantaneous accesses. Overlapping accesses appear primarily in code that uses in-out parameters or mutating methods of a structure, as the next sections show.

Conflicting access to in-out parameters

A function has long-term write access to all of its in-out parameters. The write access starts after all of the non-in-out parameters have been evaluated and lasts for the entire duration of that function call. If there are multiple in-out parameters, the write accesses start in the same order as the parameters appear.

One consequence of this is that you can't access the original variable that was passed as in-out, even if scoping rules and access control would otherwise permit it — any access to the original creates a conflict:

var stepSize = 1

func increment(_ number: inout Int) {
    number += stepSize   // read access to stepSize during the in-out write
}

increment(&stepSize)
// Error: conflicting accesses to stepSize

stepSize is both the long-term write target and read inside the function.

Here stepSize is a global variable, and it's normally accessible from inside increment(_:). But the write access to number overlaps the read access to stepSize — both refer to the same memory location. One way to resolve the conflict is to make an explicit copy:

var copyOfStepSize = stepSize
increment(&copyOfStepSize)
stepSize = copyOfStepSize   // safe: distinct locations, no overlap

Copying first ensures the read and the write touch different memory.

Another consequence is that passing a single variable as the argument for multiple in-out parameters of the same function produces a conflict:

func balance(_ x: inout Int, _ y: inout Int) {
    let sum = x + y
    x = sum / 2
    y = sum - x
}

var playerOne = 42
var playerTwo = 30
balance(&playerOne, &playerTwo)   // OK — different locations
balance(&playerOne, &playerOne)   // Error: conflicting accesses to playerOne

The same variable can't satisfy two overlapping in-out write accesses.

Conflicting access to self in methods

A mutating method on a structure has write access to self for the duration of the method call. Consider a game where each player has health and energy, and topping up one player draws energy from another:

struct Player {
    var name: String
    var health: Int
    var energy: Int

    mutating func restoreHealth() {
        health = 100   // write access to self for the whole method
    }

    mutating func shareHealth(with teammate: inout Player) {
        balance(&teammate.health, &health)
    }
}

var oscar = Player(name: "Oscar", health: 10, energy: 10)
var maria = Player(name: "Maria", health: 5, energy: 10)

oscar.shareHealth(with: &maria)   // OK — oscar and maria are distinct
oscar.shareHealth(with: &oscar)   // Error: conflicting accesses to oscar

Calling shareHealth with oscar's own value conflicts with the method's write access to self.

The first call is fine: oscar and maria are different memory locations, so their write accesses don't overlap. The second call fails: shareHealth(with:) needs a write access to self (which is oscar) and a write access to teammate (also oscar) at the same time — two overlapping write accesses to the same location.

Conflicting access to properties

Types like structures, tuples, and enumerations are made up of individual constituent values, such as the properties of a structure or the elements of a tuple. Because these are value types, mutating any piece of the value mutates the whole value — which means read or write access to one property requires access to the whole value. For example, overlapping write accesses to the elements of a tuple produce a conflict:

var playerInfo = (health: 10, energy: 20)
balance(&playerInfo.health, &playerInfo.energy)
// Error: conflicting access to properties of playerInfo

Both arguments require overlapping write access to the whole tuple.

The same restriction applies to the stored properties of a structure stored in a global variable:

var holly = Player(name: "Holly", health: 10, energy: 10)
balance(&holly.health, &holly.energy)
// Error: conflicting access to properties of holly

Overlapping write access to two properties of the same global structure.

In practice, most access to the properties of a structure can overlap safely. The compiler can prove that overlapping access to properties of a structure is safe — meaning it allows it — if all of these hold:

func someFunction() {
    var oscar = Player(name: "Oscar", health: 10, energy: 10)
    balance(&oscar.health, &oscar.energy)   // OK — local, stored, not escaping
}

When the compiler can prove exclusivity locally, overlapping property access is allowed.

Tip: if the compiler can't guarantee safety, it rejects the access. The conditions above are exactly the situations where it can prove that two properties refer to disjoint storage. When you hit one of these errors, the simplest fix is usually to copy the value into a local variable, perform the work, and copy it back.

Wrapping up

Swift's exclusive-access rule — at most one write, or any number of reads, to a given memory location at one time — is what keeps your single-threaded code free of surprising aliasing bugs. Most of the time these checks are invisible because instantaneous accesses can't overlap. They surface mainly around inout parameters and mutating methods, and the fix is almost always to give each access its own copy of the value.