Solving “reference to var myVariable is not concurrency-safe because it involves shared mutable state” in Swift

Published on: August 15, 2024

Once you start migrating to the Swift 6 language mode, you'll most likely turn on strict concurrency first. Once you've done this there will be several warings and errors that you'll encounter and these errors can be confusing at times.

I'll start by saying that having a solid understanding of actors, sendable, and data races is a huge advantage when you want to adopt the Swift 6 language mode. Pretty much all of the warnings you'll get in strict concurrency mode will tell you about potential issues related to running code concurrently. For an in-depth understanding of actors, sendability and data races I highly recommend that you take a look at my Swift Concurrency course which will get you access to a series of videos, exercises, and my Practical Swift Concurrency book with a single purchase.

WIth that out of the way, let's take a look at the following warning that you might encounter in your project:

reference to var myVariable is not concurrency-safe because it involves shared mutable state

There are multiple reasons for this warning to pop up in Xcode. For example, the code below would cause Xcode to warn us:

// Var 'myVariable' is not concurrency-safe because it is nonisolated global shared mutable state; this is an error in the Swift 6 language mode
var myVariable = UUID()

func randomCharacter() async -> Character {
    myVariable = UUID()
    return myVariable.uuidString.randomElement() ?? "1"
}

The following code makes myVariable a static var which results in the same warning being shown:

struct CharacterMaker {
    // Var 'myVariable' is not concurrency-safe because it is nonisolated global shared mutable state; this is an error in the Swift 6 language mode
    static var myVariable = UUID()

    static func randomCharacter() async -> Character {
        myVariable = UUID()
        return myVariable.uuidString.randomElement() ?? "1"
    }
}

The Swift compiler considers any globally accessible var to be unsafe from a concurrency point of view. The reason for that is that nothing is preventing us from making multiple calls to randomCharacter concurrently which would result in a data race on myVariable. We'd end up with multiple read and write operations at the same time.

To fix this, myVariable should either be moved into an actor or be isolated to a global actor.

For example, you could isolate myVariable to @MainActor like this:

// with a global variable
@MainActor
var myVariable = UUID()

// or as a static property
struct CharacterMaker {
    @MainActor
    static var myVariable = UUID()
    // ...
}

The downside of this is, of course, that we need to be on the main actor to interact with the variable. You can work around this by defining your own (empty) global actor which will ensure that our accesses are on the global executor instead of the main actor:

@globalActor
actor GlobalIsolator {
  static let shared = GlobalIsolator()
}

@GlobalIsolator
var myVariable = UUID()

// or as a static property
struct CharacterMaker {
    @GlobalIsolator
    static var myVariable = UUID()
    // ...
}

This makes accessing myVariable a bit less convenient because you'll need to place yourself on the GlobalIsolator actor when interacting with myVariable:

@GlobalIsolator
static func randomCharacter() async -> Character {
    myVariable = UUID()
    return myVariable.uuidString.randomElement() ?? "1"
}

In some cases you'll know that even though the compiler doesn't like your shared mutable state, you know that it's fine due to the way your code is structured.

If that's the case, and you're absolutely 100% sure that you won't have any issues related to your shared mutable state, you can use nonisolated(unsafe) on your variable to tell the compiler that the lack of isolation is intentional and that you're aware of its data safety issues:

// with a global variable
nonisolated(unsafe) var myVariable = UUID()

// or as a static property
struct CharacterMaker {
    nonisolated(unsafe) static var myVariable = UUID()
    // ...
}

You should only use nonisolated(unsafe) as a last-resort solution because the compiler will no longer be able to help you detect possible data races around myVariable.

Subscribe to my newsletter