How to adjust forEachAsync for use within TestActor without a warning? It should stay reusable in other places.
I need TestActor to pass around non-sendable stuff within it. And I want to use higher-order convenience functions like forEachAsync within it. Execution is sequential on purpose.
Build settings -> Strict Concurrency Checking: Complete
class NotSendable { }
actor TestActor {
func foo() async {
// No problem
for _ in 1...3 {
await bar(NotSendable())
}
// Warning: Passing argument of non-sendable type '(Int) async -> ()'
// outside of actor-isolated context may introduce data races
await (1...3).forEachAsync { _ in
await bar(NotSendable())
}
}
func bar(_: NotSendable) async { }
}
extension Sequence {
func forEachAsync(_ operation: (Element) async -> Void) async {
for element in self {
await operation(element)
}
}
}
As of Swift 5.7,
asyncfunctions not isolated to any actor will “formally run on a generic executor associated with no actor.” The rationale for this is outlined in SE-0338 – Clarify the Execution of Non-Actor-Isolated Async Functions.So, in this case, the non-isolated
asyncfunction,forEachAsync, will not run onTestActor, but rather on a generic executor, thus introducing a switch in concurrency context. The aforementioned Swift Evolution proposal acknowledges that the old behavior (of staying on the current actor, if any) was effective in “minimizing switches between executors”, but they concluded it was worth this cost for the reasons outlined in SE-0338.Technically, you can achieve what you want with global actors, but it is ugly and largely defeats the purpose of the simple
extension. You might be able to write a macro that does this expansion for you, but it is likely not worth it.I know this is not the answer you are looking for, but the simplest solution is to just stick with the
for-inloop. Or, if practical, revisit whether you might just make the objectSendable(and add the missing@Sendablequalifier to the closure parameter of that function).