swiftswift6

Access actor isolated value from another actor isolated context in Swift 6


I have the following code example

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

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

@GlobalActor
protocol MyProtocol {
    var someValue: Int { get }
    func doSomething() async
}

class MyClass: MyProtocol {
    var someValue: Int = 0
    
    func doSomething() async {
        print("Hello")
    }
}

@ControllerActor
protocol Controller {
    var myProtocol: MyProtocol? { get }
}

extension Controller {
    func doControllerWork() async  {
        await myProtocol?.doSomething()
    }
}

MyProtocol is isolated by GlobalActor, while the Controller protocol is isolated by ControllerActor. When I try to call await myProtocol?.doSomething(), I get the following error:

Sending global actor 'ControllerActor'-isolated value of type 'any MyProtocol' with later accesses to global actor 'GlobalActor'-isolated context risks causing data races.

This error disappears if doControllerWork is implemented within a class that conforms to the Controller protocol. However, in my scenario, doControllerWork is shared across multiple controllers implementations.

The issue is also resolved if I use the same actor isolation for both MyProtocol and Controller. Unfortunately, they have very different scopes within my project, so I would like to keep them isolated under separate actors.


Solution

  • Let me write out your code a bit more explicitly:

    if let p = myProtocol {
        await p.doSomething()
    }
    

    There are three important facts here:

    This means that p is transferred between contexts. It therefore must be Sendable. The good news is that this is already true, since all MyProtocol-conforming types are isolated to a global actor. Swift really should be able to infer this automatically, but currently cannot.

    The fix is to explicitly require Sendable:

    @GlobalActor
    protocol MyProtocol: Sendable { ... }
    

    This does not require any changes to conforming types since they already are Sendable. It just lets the compiler know this is true.