Conflicting access to memory in Swift

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 concurrent or multithreaded code.

A conflicting access to memory can occur when different parts of your code are trying to access the same location in memory at the same time. Multiple accesses to a location in memory at the same time can produce unpredictable or inconsistent behavior.

If you have conflicting access to memory from within a single thread, Swift guarantees that you’ll get an error at either compile time or runtime. For multithreaded code, use Thread Sanitizer to help detect conflicting access across threads.

There are three characteristics of memory access to consider in the context of conflicting access:

  • Whether the access is a read or a write (a write access changes the location in memory, but a read access doesn’t).
  • Duration of the access (instantaneous or long-term).
  • Location in memory being accessed (variable, constant, or property).

A conflict occurs if you have two accesses that meet all of the following conditions:

  • At least one is a write access or a nonatomic access.
  • They access the same location in memory.
  • Their durations overlap.

The difference between instantaneous access and long-term access is that it’s possible for other code to 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 instantaneous accesses.

Overlapping accesses appear primarily in code that uses in-out parameters in functions and methods or mutating methods of a structure.

Conflicting Access to In-Out Parameters

A function has long-term write access to all of its in-out parameters. The write access for an in-out parameter 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.

You can’t access the original variable that was passed as in-out, even if scoping rules and access control would otherwise permit it.

var stepSize = 1

func increment(_ number: inout Int) {
  number += stepSize
}

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

You can’t pass a single variable as the argument for multiple in-out parameters of the same function.

func balance(_ x: inout Int, _ y: inout Int) {
  let sum = x + y
  x = sum / 2
  y = sum - x
}
var playerOneScore = 42
var playerTwoScore = 30
balance(&playerOneScore, &playerTwoScore) // OK
balance(&playerOneScore, &playerOneScore) // Error

Conflicting Access to self in Methods

A mutating method on a structure has write access to self for the duration of the method call.

extension Player {
  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.shareHealth(with: &oscar) // Error

The mutating method needs write access to self for the duration of the method, and the in-out parameter needs write access to teammate for the same duration. Within the method, both self and teammate refer to the same location in memory.

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, meaning read or write access to one of the properties requires read or write access to the whole value.

var playerInformation = (health: 10, energy: 20)
balance(&playerInformation.health, &playerInformation.energy) // Error

Compiler can prove that overlapping access to properties of a structure is safe if the following conditions apply:

  • You’re accessing only stored properties of an instance, not computed properties or class properties.
  • The structure is the value of a local variable, not a global variable.
  • The structure is either not captured by any closures, or it’s captured only by nonescaping closures.
func someFunction() {
  var oscar = Player(name: "Oscar", health: 10, energy: 10)
  balance(&oscar.health, &oscar.energy)  // OK
}