I doubt your explanation. If fn1 in the following example was in non-actor domain, then bar would have run on global executor.
class NS {}
func bar() {}
actor A {
let ns = NS()
func foo() {}
func test() async {
let fn1: @concurrent () async -> Void = {
self.assertIsolated() // OK
bar()
await self.foo() // `await` isn't needed
}
let fn2: @MainActor () async -> Void = {
MainActor.shared.assertIsolated() // OK
bar()
await self.foo()
}
await fn1()
await fn2()
}
}
let a = A()
await a.test()
The above code actually produces a warning about await self.foo() line fn1:
warning: no 'async' operations occur within 'await' expression
That indicates compiler thinks fn1 is self-isolated, not nonisolated.
I guess this behavior is an unexpected side effect of SE-0461. Before Swift 6.2, there was no way to explicitly specify a closure is nonisolated. A nonisolated closure could only be inferred and, when the closure captured isolated parameter, it's inferred to have the same isolation as the parameter. All worked perfectly. After SE-0461 it's possible to specify @concurrent and nonisolated(nonsending) explicitly and thus the conflict.
// Try this on releases before 6.2
let fn1: nonisolated () async -> Void = { } // invalid
let fn2: @MainActor () async -> Void = { } // ok
BTW, the more I think about it, the more I think a closure capturing actor-isolated values (including properties, local variables, and parameters) should be Sendable, for the same reason why a global actor isolated closure is Sendable. The only difference between them is that the former runs on an instance of a custom actor and the latter runs on the shared instance of a global actor.