Reviews are an important part of the Swift evolution process. All review feedback should be either on this forum thread or, if you would like to keep your feedback private, directly to the review manager. When emailing the review manager directly, please keep the proposal link at the top of the message.
What goes into a review?
The goal of the review process is to improve the proposal under review through constructive criticism and, eventually, determine the direction of Swift. When writing your review, here are some questions you might want to answer in your review:
What is your evaluation of the proposal?
Is the problem being addressed significant enough to warrant a change to Swift?
Does this proposal fit well with the feel and direction of Swift?
If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?
How much effort did you put into your review? A glance, a quick reading, or an in-depth study?
More information about the Swift evolution process is available at
While I do not feel qualified to seriously evaluate this proposal, I do want to state that I am in favor of clarification on this behavior (not surprising, since I ran into this issue in production). Also, a big thank you to everyone who addressed this issue so quickly, especially @kavon!
Yes, that is correct. With a call (whether async or synchronous), the caller and callee are always part of the same task. When both are non-isolated (or both have the same actor isolation), the call won't let the data escape into another actor, either.
Right. Non-Sendable values require all their uses to be totally ordered by the happens before relation. Tasks totally order their execution, so if a non-Sendable value is isolated to a single task, that's good enough. Actors totally order the execution of their actor-isolated functions, so if a non-Sendable value is isolated to a single actor, that's also good enough. Swift doesn't currently distinguish these things at a precise enough level to allow e.g. an actor function to take a task-isolated parameter; instead, we just assume that all non-Sendable values accessible in an actor-isolated function are meant to be isolated to the actor. As long as that's true, we need to restrict non-Sendable values from crossing an actor isolation boundary on a task; but there's no need to restrict values from being passed around in non-isolated code.
Does the non-actor-isolated global executor serialize access (like an actor executor) or allow parallelism?
This has been discussed but it is still unclear to me. Re:the caller and callee are both known to be non-actor-isolated. I understand why that is ok within the same Task, but can you clarify if that non-Sendable args and results can be used from different Tasks like this example:
class Test {
var int = 0
}
func foo(_ arg: Test) async {
arg.int = Int.random(in: (0..<100))
}
let test = Test()
Task.detached {
await foo(test)
}
Task.detached {
await foo(test)
}
I think this proposal will do an excellent job of resolving a confusing corner-case of Swift's concurrency model. The existing "sticky" behavior of non-isolated async functions has been the subject of at least one bug report because the reasoning for that behavior wasn't intuitive.
Is the problem being addressed significant enough to warrant a change to Swift?
Absolutely. If it were not resolved, I think programmers would keep running into hard-to-debug "overhang" issues.
Does this proposal fit well with the feel and direction of Swift?
Yes.
If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?
N/A
How much effort did you put into your review? A glance, a quick reading, or an in-depth study?
I did a quick read and also helped push this issue into the spotlight.
No, it does not serialize execution. I should clarify that the non-actor-isolated global executor is not a new concept; it is where new tasks begin running if they do not inherit an actor to be isolated to, as well as where tasks resume running after various kinds of suspension.
No, they cannot. Here's how that restriction works:
In general, it's fine to pass a non-Sendable value from a non-isolated async function (like the closure passed to Task.detached to another non-isolated async function (like foo).
In this case, since test is a value from an enclosing function, the closure passed to Task.detached must capture it.
A closure that captures a non-Sendable value must itself be non-Sendable.
But Task.detached must be passed a Sendable function, so this is ill-formed.
This restriction on captures is not new; it's always been part of how Sendable works.
I agree that async calls from an actor-isolated function should not be guaranteed to run on the actor's executor. Aspects of the actor design (such as reentrancy) suggest to me that they are designed for maximum throughput while allowing safe access to isolated data, not for performing suspending operations on the actor's executor - i.e. it seems to me that the best way to use an actor is to quickly read any state you need up-front, perform any operations with that data on your own time, then return to the actor only to update its state.
I agree that all arguments and return values of async functions must be Sendable. I actually had a draft reply to the Improving Sendable pitch which arrived at the same conclusion. Unfortunately I was distracted by other things and forgot to post it (I sometimes write draft replies, then leave them to stew for a bit while I think about things a bit more). Here was my reasoning, which is more about the underlying threads tasks are ultimately scheduled on and types with reference semantics than the more formal reasoning in this proposal:
One thing that I've been wondering about (and perhaps this is the right place to bring it up) is whether we should require that arguments to allasync functions are Sendable.
My understanding is that we want Sendable enforcement to eventually become a guarantee against data races. If you pass some data in to an async function, we have no idea whether or not there are other references to its memory, whether it has been passed to any other async functions, and whether any of them will mutate the memory's contents. And when the function you are calling suspends, we don't know which thread it's ultimately going to resume on, and whether or not its reads will be serialised with writes from other functions or overlap with them.
The thing that gives us those guarantees is Sendable.
So yeah, these look like good, important changes to me.
Hey all, thank you for your reviews here and on the pitch thread. The Core Team has accepted this proposal and a proper announcement will be coming soon.
Please correct me if this isn’t appropriate to ask here, but we are trying to build an architecture with Swift concurrency and are puzzled how to do this after this change. I described our issue here: How to create an object graph with… | Apple Developer Forums