This error is actually an example of what I call a "fallback error"... where the compiler detects the error but fails to infer a name or source location for the actual value that is being sent. We have to fail and we have to emit a message... but if we cannot determine the actual value that is being sent, this is the best that we can do...
That being said, the emission of this error is a form of a bug. The methodology that we use to discover the name/information about the variable that is causing the problem is failing... so a bug report would be appreciated especially if it does reproduce with a ToT compiler. = ).
This is a known issue that was already fixed on main. We now emit an error that has the source level type. On main, we now emit the following:
test.swift:11:19: error: sending value of non-Sendable type '() async -> Bool' risks causing data races
9 | // -> @out τ_0_0 for <Bool>' accessed after being
10 | // transferred; later accesses could race.
11 | group.addTask {
| |- error: sending value of non-Sendable type '() async -> Bool' risks causing data races
| |- note: Passing value of non-Sendable type '() async -> Bool' as a 'sending' argument to instance method 'addTask(priority:operation:)' risks causing races in between local and caller code
| `- note: access can happen concurrently
12 | print(ns)
13 | return false
NOTE: the actual error here is that from a control flow perspective, we are accessing the region of the array multiple times as we go around the loop potentially in different tasks. The compiler does not know that the array only has one element. That is why the "access can happen concurrently" note is on addTask. It is actually the use in the next iteration. This can be seen if one unrolls the loop as follows:
class NonSendable {}
func test() async {
await withTaskGroup(of: Bool.self) { group in
let x = [NonSendable()]
group.addTask {
print(x)
return false
}
group.addTask {
print(x)
return false
}
await group.waitForAll()
}
}
Error:
test.swift:7:11: error: sending value of non-Sendable type '() async -> Bool' risks causing data races
5 | await withTaskGroup(of: Bool.self) { group in
6 | let x = [NonSendable()]
7 | group.addTask {
| |- error: sending value of non-Sendable type '() async -> Bool' risks causing data races
| `- note: Passing value of non-Sendable type '() async -> Bool' as a 'sending' argument to instance method 'addTask(priority:operation:)' risks causing races in between local and caller code
8 | print(x)
9 | return false
10 | }
11 | group.addTask {
| `- note: access can happen concurrently
12 | print(x)
13 | return false
Thanks, this is helpful. It looks like the error message for this specific case is indeed much improved on a current main toolchain. I tested with this code snippet:
class Foo {
let value: Int = 42
func foo() {
Task { @MainActor in
print(value)
}
}
}
With the shipping Swift 6.0.3 compiler we get the fallback error message:
$ swift --version
swift-driver version: 1.115.1 Apple Swift version 6.0.3 (swiftlang-6.0.3.1.10 clang-1600.0.30.1)
Target: arm64-apple-macosx15.0
$ swiftc -swift-version 6 code.swift
code.swift:5:14: error: task or actor isolated value cannot be sent
3 |
4 | func foo() {
5 | Task { @MainActor in
| `- error: task or actor isolated value cannot be sent
6 | print(value)
7 | }
With a semi-current main toolchain (I tested with 2024-12-13 because that was the one I had already installed) we get a much better diagnostic:
$ swift --version
Apple Swift version 6.2-dev (LLVM be8c96d78337932, Swift 0bbaa3519b62071)
Target: arm64-apple-macosx15.0
$ swiftc -swift-version 6 code.swift
code.swift:6:19: error: sending 'self' risks causing data races
4 | func foo() {
5 | Task { @MainActor in
6 | print(value)
| |- error: sending 'self' risks causing data races
| `- note: task-isolated 'self' is captured by a main actor-isolated closure. main actor-isolated uses in closure may race against later nonisolated uses
7 | }
8 | }