`TaskGroup` and `sending` closures

@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.

2 Likes