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.)