Search code examples
iosswift

Swift 6 Error with Non-Isolated Global Shared Mutable State in EnvironmentKey


I've encountered a concurrency safety issue in Swift 5.10 StrictConcurrency mode that I'm struggling to resolve. I'm working with an EnvironmentKey structure that includes a static property defined as an asynchronous closure returning an optional custom actor. Here is the simplified code snippet:

struct DataHandlerKey: EnvironmentKey {
    static var defaultValue: @Sendable () async -> Hello? = { nil }
}

actor Hello {}

The defaultValue is a closure marked with @Sendable that asynchronously returns an optional Hello actor instance. However, Swift 5.10 with StrictConcurrency compiler raises a concurrency safety error, stating:

Static property 'defaultValue' is not concurrency-safe because it is non-isolated global shared mutable state; this is an error in Swift 6.

I understand that the issue is related to the static property potentially introducing non-isolated global shared mutable state, but I'm unsure how to adjust my code to adhere to Swift 6's enhanced concurrency safety requirements. The Hello actor is designed to be concurrency-safe, yet I'm unable to use it as intended in this context.

Any insights, suggestions, or references to relevant documentation would be greatly appreciated. Thank you in advance for your help!


Solution

  • defaultValue should be a let (or a computed property). It doesn't need to be a var:

    static let defaultValue: @Sendable () async -> Hello? = { nil }
    

    If it is a var, any code from any thread can assign to it and change its value, and since it is not isolated to an actor, this can cause data races.

    What allows you to change environment values is not that defaultValue is a var, but that the EnvironmentValues property has a setter.

    extension EnvironmentValues {
        var dataHandler: @Sendable () async -> Hello? {
            get { self[DataHandlerKey.self] }
            // this setter is what allows you to chang the environment
            set { self[DataHandlerKey.self] = newValue }
        }
    }