The core recommendation made to solve the major problem discussed in this whitepaper appears to be (with my own added emphasis):
- The first time [global state declared with
let
orvar
] is touched by within actor context, it is lazily initialized and stored in a per-actor dictionary, and subsequent accesses retrieve this. This makes globals syntactic sugar for being defined as instance variables on the actor themselves. These values are destroyed when the actor is destroyed.- The first time [that same global declaration] is touched by something outside an actor context, an actually-global instance is created and maintained in process global state just as it is today.
I think this creates a new area of confusion for mutable state: the exact same global var
or let
declaration simultaneously embodies two completely separate kinds of state based on the context of the declaration's uses. It functions either as an ordinary global or actor-instance member.
The familiar concept of stored members of classes / structs being unique per-instance is tied to the scope in which those members are defined, not based on their use sites. That is, per-instance storage is explicitly scoped to the class definition to make things clear to programmers. The whitepaper's recommendation breaks that convention and allows this code:
private var context = BuilderContext()
public func createThing(size: Int) -> Thing {
// reference to standard global `context`
return context.createThing(size)
}
actor class Actor {
func getThing() -> Thing {
// does *not* refer to the same `context` as in `createThing`,
// `context` is silently an instance variable of Actor!
return context.createThing(size)
}
}
The only language I can think of that has such a subtle use-site treatment for variables is Python's global vs local distinction, where an assignment to a global within a function, without the global
designation, changes that variable into a local for that function. While surprising for newcomers at first, having such a rule makes sense for Python because it resolves an ambiguity: an assignment is the only way to introduce a function-local variable, but the chosen name may clash with one already in global scope and thus could also be interpreted as an accidental mutation of that global.
For us to create such a surprising functionality in Swift when it comes to uses of globals from actor-isolated state vs non-isolated state, I think we need to have some very solid motivations.
The whitepaper doesn't appear to discuss the current implementation of the Swift concurrency proposal, so I wanted to lay out the details here. Currently, there is checking for mutable stored globals (aka global properties declared with var
) that works like so:
- If the global is marked with
@actorIndependent(unsafe)
, then the global can be freely accessed from actor-isolated contexts without any warning or error. - The global cannot be marked with
@actorIndependent
because it's not possible to guarantee safety. - Otherwise, for accesses to the global from an actor-isolated context, a warning is emitted for each access since it is possibly unsafe.