`TaskGroup` and `sending` closures

I can demonstrate a data race in your approach. I think it's a bug.

struct Box<Value> {
  var value: Value?

  mutating func take() -> sending Value {
    if let value {
      self.value = nil
      return value
    } else {
      preconditionFailure("Consumed twice")
    }
  }
}

func foo(_ closure: @escaping () async -> Void) async {
  var bar1 = Box(value: closure)
  var bar2 = Box(value: closure)
  await withTaskGroup { taskGroup in
    let closure1 = bar1.take()
    let closure2 = bar2.take()
    taskGroup.addTask {
      await closure1()
    }
    taskGroup.addTask {
      await closure2()
    }
  }
}

EDIT: I think there are at least two separate issues. One is what you mentioned: closure parameter should be marked as sending. Another is that, even if it's a sending closure, it's still possible to create data race by using your Box struct. I've filed #83121 for the second issue.

2 Likes