@jamieQ's workaround looks like a bug (or limitation?) in compiler, because I can use this approach to successfully compile code having data race.
Example 1: the clsoure passed to send()
runs concurrently in taskGroup.addTask()
and withTaskGroup()
.
func send(
operation: sending @escaping () async -> Void
) async {
let box = { operation }
await withTaskGroup(of: Void.self) { taskGroup in
let operation = box()
taskGroup.addTask {
await operation()
}
await box()() // closure runs concurrently but it compiles
}
}
Example 2: This is based on an example in "Task Isolated Regions" section in SE-0414. The transferToMainActor()
call should fail, but it compiles in the code below.
class NonSendable {}
@MainActor func transferToMainActor(_ x: NonSendable) async { }
func nonIsolatedCallee(_ x: NonSendable) async { }
func nonIsolatedCaller(_ x: NonSendable) async {
await nonIsolatedCallee(x) // this is ok (as expected)
let box = { return x }
await transferToMainActor(box()) // this should fail but it compiles
await nonIsolatedCallee(x) // x is accessed concurrently in both MainActor and global executor.
}
EDIT: I filed #79262.