Hello,
Here is a snippet of code that compiles with Swift 6.1 (Xcode 16.3 beta 2 (16E5121h)), but not with Swift 6.0 (Xcode 16.2):
struct Service: Sendable {
func perform(_ action: sending @escaping () -> Void) async { }
}
@MainActor func caller(_ service: Service) async {
// β Swift 6.0: Main actor-isolated value of type '() -> ()' passed
// as a strongly transferred parameter; later accesses could race
// β
Swift 6.1 (Version 16.3 beta 2 (16E5121h)): no error
await service.perform { print("Hello") }
}
Note that:
Service
is Sendable, so that it can be used from any isolation domain.Service.perform
is async and nonisolated so that it runs on the shared thread poolaction
issending
because this makes it able to capture non-sendable values.action
is@escaping
because it will be run eventually.action
is intended to be nonisolated becauseperform
does not want to make any promise (in practice, it's somewhat related to task groups which may run tasks in parallel)
According to the Swift 6.0 compiler error, it looks like the closure that prints "Hello" is MainActor-isolated, despite the lack of such requirement in the declaration of perform
. I suppose this isolation is inferred from the isolation of the enclosing caller
function.
With the Swift 6.1 compiler, it is a different story. This closure is nonisolated (as I want it to be). We can even make sure that the Swift 6.1 sees this closure as nonisolated, by forcing an error:
struct Service: Sendable {
func perform(_ action: sending @escaping () -> Void) async { }
}
@MainActor let global = "Hello"
@MainActor func caller(_ service: Service) async {
await service.perform {
// (As expected with Swift 6.1) Main actor-isolated let
// 'global' can not be referenced from a nonisolated context.
print(global)
}
}
Because I'm confused by the behavior change, even if I think I know pretty well what is the behavior I desire, I have a few questions for the concurrency experts:
-
Is this an expected change, or a regression (bug) in Swift 6.1 beta? I hope it is an expected change, but I can't see anything related in the proposals that have shipped in Swift 6.1.
-
My intent is that the compiler never, never, never, I'm serious, never infers any isolation of the closure provided by the caller of
perform
. In other words, I want to fully exclude the possibility of compiler errors created by any kind of isolation inference. The closure must always be considered as nonisolated. Is it possible, or does the language come with some unavoidable inference rules? Do I need to add further decorations to theperform
declaration?
I know that we are all very focused on specifying specific isolations, and that it's at odds with my request, which is enforcing the lack of isolation. I hope I'll find support regardless