main1 is green but main2 is red. I want to create a "transparent" runBlock function that just calls its closure, but I have no luck because of the error.
If I drop @MainActor from main2, then the diagnostic disappears. I use Swift 6.0.3.
Apparently, Swift doesn't have problems with sending ns from main2 to the closure. It has problems with sending ns from the closure to await foo. But if it's the case then main1 shouldn't work, should it?
Right now, I tend to think that it's a false positive diagnostic, but I may be missing something
Actually, now when nonisolated is explicitly spelled out, the whole situation confuses me even more. So Swift doesn't complain when it's nonisloated, but it does complain when func block() is isolated to @MainActor.
Isn't the isolation to @MainActor better than being nonisolated? In case of isolation to @MainActor, ns doesn't cross any isolation domains, it always stays in the MainActor isolation domain -- there are no races.
Uh, this is tricky, but I think I know what's going on here.
By using isolation: isolated (any Actor)? = #isolation, you're telling the compiler that runBlock runs in the same isolation domain as the caller. However (and this is the tricky bit), the compiler doesn't know that block should also run on the same isolation domain as the caller.
This is more or less the exact issue described as one of the motivation examples for Run nonisolated async functions in the caller actor by default (SE-0461). Another clue that this is the underlying issue is that annotating block with the (underscored, not stable) attribute @_inheritActorContext works:
So the compiler sees that runBlock has no specific isolation requirements for its block parameter, therefore it must do sendability checks for the closure. And then it detects that the closure you're passing to block includes a non-sendable value.
The only way to pass that non-sendable value across isolation domains is by sending it. But although sending works in a function, for example in your main1 function:
It doesn't work for the closure. Generally speaking, sending a value to a function as an argument is more reliable than creating a closure where that same value is sent to the function and passing that closure as an argument to a function.
That's because the function that receives the closure is allowed (depending on a combination of other factors, like whether the closure is @escaping/async/@Sendable) to do weirder things with the closure, like calling it more than once or calling it multiple times in parallel which may mess up the logic of whether some value can be sent.