- Authors: John McCall, Sophia Poirier
- Implementation: On
-strict-concurrency=complete -enable-experimental-feature GlobalConcurrency
- Previous Proposals: SE-0302, SE-0306, SE-0316, SE-0337
This proposal defines options for the usage of global variables free of data races. Within this proposal, global variables encompass any storage of static duration:
lets and stored
vars that are either declared at global scope or as static member variables.
Global state poses a challenge within concurrency because it is memory that can be accessed from any program context. Global variables are of particular concern in data isolation checking because they defy other attempts to enforce isolation. Variables that are local and un-captured can only be accessed from that local context, which implicitly isolates them. Stored properties of value types are already isolated by the exclusivity rules. Stored properties of reference types can be isolated by isolating their containing object with sendability enforcement or using actor restrictions. But global variables can be accessed from anywhere, so these tools do not work.
Under strict concurrency checking, require every global variable to either be isolated to a global actor or be both:
Global variables that are immutable and
Sendable can be safely accessed from any context, and otherwise, isolation is required.
These requirements can be enforced in the type checker at declaration time.
Due to the addition of restrictions, this could require changes to some type declaration when strict concurrency checking is in use. Such source changes however would still be backwards compatible to any version of Swift with concurrency features.
This proposal does not add or affect ABI in and of itself, however type declaration changes that it may instigate upon an adopting project could impact that project's ABI.
Some global variable types may need to be modified in a project adopting strict concurrency checking.
For isolation, rather than requiring a global actor, we could implicitly lock around accesses of the variable. While providing memory safety, this can be problematic for thread safety, because developers can easily write non-atomic use patterns:
// value of global may concurrently change between // the read for the multiplication expression // and the write for the assignment global = global * 2
Though we could consider implicit locking if we needed to do something source-compatible in old language modes, generally our approach has just been to say that old language modes are concurrency-unsafe. It also would not work for non-
Sendable types unless we force the value to remain isolated while accessing it. We potentially could accomplish that with the proposed Safely sending non-Sendable values across isolation domains feature, but that is probably too advanced a feature to push as a solution for such a basic problem.
We could default all global variables that require isolation to
@MainActor. It is arguably better to make developers think about the choice (e.g. perhaps it should just be a
Access control is theoretically useful here: for example, we could know that a global variable is concurrency-safe because it is private to a file and all of the accesses in that file are from within a single global actor context, or because it is never mutated. That is a more global analysis than we usually want to do in the compiler, though; we would have to check everything in the context, and then it might be hard for the developer to understand why it works.
We do not necessarily need to require isolation to a global actor to be explicit; there is room for inferring the right global actor. A global mutable variable of global-actor-constrained type could be inferred to be constrained to that global actor (though unnecessary if the variable is immutable, since global-actor-constrained class types are