`@isolated(any)` function types

Swift reserves the right to optimize the execution of tasks to avoid "unnecessary" isolation changes, such as when an isolated async function starts by calling a function with different isolation. In general, this includes optimizing where the task initially starts executing. As an exception, in order to provide a primitive scheduling operation with stronger guarantees, Swift will always start a task function on its appropriate executor for its formal dynamic isolation unless:

  • it is non-isolated or
  • it comes from a closure expression that is only implicitly isolated to an actor (that is, it has neither an explicit isolated capture nor a global actor attribute).

I worry about this difference between implicit and explicit closures. If I understand correctly, this means the following:

@MainActor
struct Example {

  // These tasks have no guarantee as to their
  // execution order on the main actor
  func test1() {
    Task { /* implicitly @MainActor via enclosing struct */
      print("Unordered One")
    }
 
    Task { /* implicitly @MainActor via enclosing struct */
      print("Unordered Two")
    }
  }

  // These tasks are guaranteed to execute in 
  // source order:
  func test2() {
    Task { @MainActor in
      print("Ordered One")
    }
 
    Task { @MainActor in
      print("Ordered Two")
    }
}

This means that there is now an observable semantic difference between implicitly inheriting an actor isolation and explicitly declaring the same isolation that would be explicitly inherited. I don't know of anywhere else in the language where explicitly redeclaring an inherited attribute has visible semantic differences. That seems likely to be a source of confusion, and I'd like to understand better the motivation behind this proposal.

(Or, if I'm understanding incorrectly, I'd love to understand better what is being proposed.)

3 Likes