Function parameter isolation not respected

Good Day everyone,

Why does the function parameter work despite being @MainActor isolated, run on the global concurrent executor when passed a non-isolated function directly as an argument instead of as a trailing closure when called from a non-isolated context:

func greet() {
  MainActor.shared.preconditionIsolated()
  print("Hello, World!")
}

func worker(work: @MainActor () async -> Void) async {
  await work()
}



Task.detached {
  await worker {
    greet() // Okay
  }

  await worker(work: greet) // Not okay, preconditionIsolated crash
}
3 Likes

without having investigated too deeply, this looks like a bug to me. i would expect greet to be wrapped in something that would perform the main actor isolation switch before it is invoked.

I would think so too, from my current Swift concurrency understanding, this seems awfully inconsistent for it to be intended behavior

filed [concurrency]: implicit function conversions can fail to preserve actor isolation ยท Issue #85554 ยท swiftlang/swift ยท GitHub to track this. i think this has to be a bug because if you explicitly split the conversion up into two steps โ€“ first attach the isolation, then convert to async โ€“ it works:

nonisolated func twoStepCvt(
    _ op: @escaping @MainActor () -> Void
) -> @MainActor () async -> Void { op }

await worker(work: twoStepCvt(greet)) // โœ…
// or inline the coercions...
await worker(work: (greet as @MainActor () -> Void) as @MainActor () async -> Void) // โœ…

FYI @hborla @John_McCall โ€“ do let us know if you think this assessment is incorrect.

3 Likes