Requirements:
- Data structure that combines a key-path to a property isolated to a global actor with a closure isolated to the same global actor.
- Data structure should be sendable.
- Key path and closure can be used synchronously after dynamically checking that current isolation is the one they are isolated to.
- The same data structure should work with different actors (open set).
- It should be possible to create instance of the data structure without being isolated to the right actor.
I was able to solve the first 4 with the following approach:
// Non-sendable
struct Info {
var keyPath: PartialKeyPath<Foo>
var action: () -> Void
}
public struct IsolatedBox<T>: @unchecked Sendable {
public let isolation: any Actor
private var value: T
public init(_ value: T, isolation: isolated any Actor = #isolation) {
self.isolation = isolation
self.value = value
}
public func open(isolation: isolated any Actor = #isolation) -> T? {
guard self.isolation === isolation else { return nil }
return value
}
}
But to construct IsolatedBox
I need to be in the correct isolation, which contradicts with the 5th requirement.
I was thinking that maybe @isolated(any)
closures could help:
var infoMakers: [@isolated(any) @Sendable () -> IsolatedBox<Info>] = {
{ @MainActor in
Info(\.mainProperty) { ... }
},
{ @AnotherActor in
Info(\.anotherProperty) { ... }
}
}
So far, so good - I can create closures from any isolation, and when they get called, I will get the right information associated to the actor. But how do I actually call the closure?
func doIt(for keyPath: PartialKeyPath<Foo>, isolation: isolated any Actor = #isolation) {
for infoMaker in infoMakers {
if infoMaker.isolation === isolation {
let infoBox = infoMaker() // error: call to @isolated(any) let 'infoMaker' in a synchronous actor-isolated context
let info = infoBox.open()! // isolation already checked above
if info.keyPath == keyPath {
info.action()
}
}
}
}
I've tried using assumeIsolated
- it did not work:
infoMaker.isolation!.assumeIsolated { _ in
infoMaker() // error: call to @isolated(any) let 'infoMaker' in a synchronous actor-isolated context
}