I encountered an issue with an actor which causes a compiler error which in my humble opinion should compile fine:
actor TestActor<Event> {
private let _send: (isolated TestActor, Event) -> Void
init(transform: @escaping @Sendable (Event) -> () async -> Void) {
var a: [UUID: Task<Void, Never>] = [:]
_send = { actor, event in
actor.assertIsolated()
let id = UUID()
a[id] = Task {
actor.assertIsolated()
let f = transform(event)
await f()
actor.assertIsolated()
a[id] = nil // <= Mutation of captured var 'a' in concurrently-executing code
}
}
}
func send(event: Event) {
_send(self, event)
}
}
The implementation probably deserves some clarification:
The actor has a closure _send as member. In order to make the closure bind to an isolated context, it has an isolated parameter.
I made the following assumption:
When the closure will be called it is isolated to the parameter actor which is actually self.
The initialiser creates a local var of type dictionary.
The local var will be captured in the closure. Since the closure is isolated to its isolated parameter actor - which is actually self - it should be safe to make mutations to the captured var a within it, even when multiple closures interleave.
There "might" be a race condition (which is not harmful in this implementation) - but there should be no data race.
Actually, the compiler will not complain mutating the captured variable a within the closure. However, it will complain when mutating it in the task created in the closure.
However, my understanding is, that a (non-detached) Tasks will inherit the actor's context, i.e. that of the isolated parameter actor (which is self). So within the task, it should again be OK to mutate the captured variable a.
I tried to confirm my assumptions with the added asserts. The asserts actually do not fail during runtime. Nonetheless, the compiler issues an error when I try to mutate the captured variable a when running in the isolated context of actor (which is self).
Note: if the actor uses a member a (instead of the captured var) - the compiler will not complain!
Running the code with the offending line commented out, the asserts will not fire.
Possibly related: https://github.com/apple/swift/issues/63773
Edit
This is the version where the captured var a is replaced with a member:
actor TestActor<Event> {
private let _send: (isolated TestActor, Event) -> Void
private var a: [UUID: Task<Void, Never>] = [:]
init(transform: @escaping @Sendable (Event) -> () async -> Void) {
_send = { actor, event in
actor.assertIsolated()
let id = UUID()
actor.a[id] = Task {
actor.assertIsolated()
let f = transform(event)
await f()
actor.assertIsolated()
actor.a[id] = nil
}
}
}
func send(event: Event) {
_send(self, event)
}
}
This compiles without errors or warnings with a standard Package for Swift 5.9 without any extra flags.
Edit:
Added @Sendable to the transform function.