Yes, "The Swift Programming Language" will need to be updated for this new language feature. In general, updates to TSPL that describe a new language feature go out at the same time as Xcode ships with that feature.
This thread is about the development of language features and questions should be posed with that in mind. In general, you're not going to get answers to questions about Apple-specific frameworks on the forums (except possibly by way of example of how the language features might be used in practice).
FWIW, I am pretty skeptical that implicit cancelation would scale and compose well. I think that explicit is more predictable and reasonable. There are strong analogies to the perils of trying to write exception safe code in C++ and pthread_cancel to learn from.
The counter argument is that await marking is already forcing you to think about suspension so it is ok to also think about cancelation on every await. I personally think that is a bridge too far -- people won't remember this, and lots of bugs will happen.
In my opinion, we should make async and throwing orthogonal from each other, and cancelation would only be possible in throwing contexts.
If youâre interested, you could look into the experience of the Zio community. They are huge advocates of designing out the potential for bugs and this is the direction they went. As far as I know they donât have any regrets about this choice.
I'm not sure what the question is, but I agree that async functions should be able to return result, and we probably want an async equivalent of Result that is Future like, for the same reason that we have both Result and try/throw.
Is there a reason for async to be after the function name, as in func refreshPlayers() async { ⌠}, that Iâve failed to notice?
In my opinion, putting async before func (i.e. async func refreshPlayers() { ⌠}) would make it impossible to overlook, and improve code readability in general: e.g. âasynchronous function refreshPlayers()â reads as English, while âfunction refreshPlayers() asynchronousâ does not.
One reason that's already been mentioned is that async (with await) is used â syntactically â a lot like throws (with try), and so there's some harmony in putting it in a similar syntactic position.
I'd also add that, although we toss around the phrase "asynchronous function", the function is not exactly executed asynchronously.
In fact, the function is started synchronously, and completes asynchronously. In that limited aspect, it makes sense to mark the result as asynchronous.
There would not be anything magic about an async function or actor returning a Result<S,F> -- you'd simply be returning that value (result) asynchronously.
We do not anticipate magically converting swift code that is an async function returning result into anything else. The story is different with existing Objective C APIs where the importer can do things, but since you mention Result I don't think you mean the obj-c interop story. The obj-c part has it's own proposal though if that's what you meant @masters3d.
The "future"-like type in this proposal is Task.Handle.
Hi Konrad! So excited for the concurrency proposals and the roadmap. Looking forward to seeing the one about async handlers too.
With regards to Task.Handle: is it a worry that many projects might want to wrap this in a type called Future - giving the same situation that was present before Result was added to the standard library? If so, would it not make sense to create such a type up front (instead of Task.Handle) - as I think Chris Lattner is also suggesting above?
As I understand our intent with the naming here is to discourage the use of Task.Handles (or "futures"), as they are not "structured" (please read the structured concurrency proposal) and lead to runoff futures which are harder to reason about.
The model Swift is attempting to recommend and push towards is more about async lets and async functions, and less about "randomly spawn a bunch of futures and never await on them" which futures can be used for (they have no limitations basically, you can await on them, but don't have to). They are super useful sometimes, but are not meant to be the default go-to solution in Swift -- async lets and async functions are.
Thus, justifying the different name so existing users don't just carry over their style of just using Futures into the new world, without reevaluating. John or Doug can also comment on this, if I represented their ideas here well, but that's how I understand our intention here and thus are ok with Task.Handle as the name.
My colleagues at Google (most notably @gribozavr, @saeta, and @pschuh) and I have been doing an in-depth review of these proposals and are ready to post some technical questions and suggestions for each one. This is the first. We are also working on a posting that discusses the overall direction and emphasis, but that may take a day or two more. Sorry this took so long and thanks for your patience; y'all have done an impressive amount of work here and it takes a while to absorb it all!
âWe can categorize memory into a few groups:âŚâ
Thereâs no mention of mutably-captured value semantic types. Is that intentional? Whatâs the plan here?
In order to âtrust the programmerâ to use unsafe memory correctly, donât we need a definition of âcorrectly,â which we understand to be the role of a memory model?
âerror: an actor cannot access another's mutable stateââAre the (mutable) properties of a let-bound actor property that is a class instance considered to be part of the actor's mutable state? I think not, and I think that will take some education to communicate.
âEven after the introduction of actors, there will still exist the possibility for race conditions, through global variables and values of reference typeââThis leaves out value types that donât have value semantics and, again, types that do have value semantics but have been mutably captured by closures. Is that intentional? Whatâs the plan here?
âClasses (and types that contain class references) will change from being from being [sic] âactor unsafeâ by default to being âactor localâ⌠compiler support for guaranteed correct "copy on write" types via a mutableIfUnique class type.ââThe former statement made me concerned about whether weâd still be able to implement CoW with this model, and the latter made me wonder what you had in mind here. Thereâs no other mention of this mutableIfUnique class type in the proposals, so how this would be handled is a bit thin. Can you please spell this out in more detail?
âA synchronous function is the kind of function that Swift programmers are already used to: it runs to completion on a single thread, with no interleaving code except for any synchronous functions it calls.ââI think âwith no interleaving codeâ glosses over _read and _modify accessors. Yes, theyâre not released features but they are in the plan and part of the communityâs mental model for how the language works, so these proposals should account for them.
âAn async function that is currently running always knows the executor on which it's running.ââwhat does âknowsâ mean? I think anthropomorphizing makes this harder to understand. Can you spell this out?
I think âinterleavedâ or âinterleavingâ would be a more appropriate adjective to apply to executors that serialize their partial tasks than âexclusive.â
The whole relationship between actors and executors is a bit fuzzy for me as a reader. We never see a representation of âexecutorâ in the type system and weâve seen that actors âact asâ executors and looking further, that executors can be âbound toâ actors. Can this relationship be clarified?
No. A low-level memory model can come along later without affecting anything in the stated roadmap.
This is a topic of discussion in the various actor isolation threads. The roadmap document isn't the place to nit-pick wording.
As you know, this is being discussed on another thread. In the roadmap, we did not seek to nail down the distinction between "value semantics" and "value types", because it is not necessary for concurrency.
This is intentionally in the "later phase" because it isn't core to the model and doesn't need to be designed now. I can't imagine a world in which we would design @mutableIfUnique to support CoW types and... not leave us in a place where we can't implement CoW types?
_read and _modify don't interleave. Just because property accessors and async functions both use coroutines as a low-level implementation mechanism does not imply that they are the same, and I think you're confusing them here.
An async function always has access to the Task it is running in.
That's flipping the default for the terminology, which is doable but doesn't actually change anything in the model. Maybe it makes sense if we change the defaults around interleaving, which is a subject of the actors thread.
Yes, we can do that in one of the proposal revisions.
Please explain how this works rather than just claiming we donât need to do anything about it. Without a definition of âcorrect;â how can I know whether Iâm doing things correctly?
First of all, I want to say that the amount of work that has gone into these proposals is absolutely awesome. Concurrency is a difficult and important topic, and the proposals show a lot of care has been invested. Thank you for the huge effort.
Within the S4TF and other Swift teams at Google, weâve been digesting these proposals together. While we have quite a few technical questions and suggestions about the proposals, we wanted to post those separately, and use this post for more qualitative feedback about the overall direction and emphasis. The thoughts below are heavily influenced by folks on those teams and other concurrency experts.
At a high level, although we have some questions around technical details of the structured concurrency and async-await proposals, weâre overall quite excited for those directions. These proposals look well thought out and appear to address important gaps in Swift. The actors proposal appears to do a good job learning from prior actor systems including Akka and Orleans. This is excellent work by the proposal authors.
That said, the very central role of actors in the proposed system, and the complexity of the language changes that support them, concern us. Paradigms that wire together networks of interacting mutable objects are familiar, butâeven without concurrencyâtend to obscure a programâs data structures and algorithms. The result can be hard to scale, debug, and reason about. Using these object-oriented paradigms as the basis for concurrencyâitself already a source of necessary complexityâcompounds the problems.
I saw this effect play out in a past role (not at Google), where I used Akka actors within a production application to coordinate background work. Unfortunately, the anticipated benefits of actors never materialized, and the few benefits we could identify were far outweighed by complexity. Based on that experience, we never used Akka actors again within our Scala applications. Specifically, we found the system difficult to test, especially when a test needs to carefully arrange the state of multiple actors. We also found understanding the business logic difficult, because the actual algorithms were distributed across child actors and hidden behind message-passing ceremony.
Among modern languages, Swift has uniquely strong and accessible support for two simple but powerful tools: generic algorithms, and types with value semantics, which can be race- and deadlock-free by construction. Frameworks like SwiftUI demonstrate that these paradigms can be used to address traditional object-oriented domains while drastically reducing the chance of programmer error. Actors, by contrast, embody reference semantics. Although they define away the inherent concurrency problems of reference semantics (at the cost of significant language complexity) they do not address any of its other issues, even adding new ones due to re-entrancy. To open Swiftâs support for concurrency with such a deep emphasis on reference semantics seems like a step in the wrong direction for the language.
Building on Swiftâs strengths, weâd prefer to:
Address key gaps in Swiftâs current fundamentals (e.g. closure captures of mutable values implicitly have reference semantics).
Allow asynchronous functions to be defined without having actors (as proposed it looks like the two features are mutually dependent).
Add structured concurrency.
Support first-class, composable, concurrent algorithms, which can be defined entirely in libraries
Even if actors turn out to be essential to the picture, we are concerned about the choice to make actors unconditionally reentrant. While this design choice defines away deadlocks, other languages make actors non-reentrant by default for a good reason: unlike data races, deadlocks are memory-safe andâgiven modern lock implementationsâeasy to debug. Conversely, the fact that actor state may shift unpredictably during awaited operations presents many of the classic hard-to-debug symptoms of race conditions.
We do appreciate the importance of interoperability with common code in the Swift and Objective-C ecosystems, and we see the obvious parallels between dispatch queues and actor message passing: actors are like serial dispatch queues with arbitrary attached state. That said, we donât fully understand how the addition of this state serves dispatch-based use cases. In order to help the community evaluate actors versus alternative approaches, would it be possible for proposers to share more about the target use cases?
Finally, weâd be remiss if we didnât mention that sharing mutable state across threads is still a thing. While we are 100% supportive of making happy-paths that use combinations of structured concurrency with value-semantic types or even actors, there exist important applications where neither actors nor raw use of value semantics can support implementations as efficient as those using locks (e.g. an in-memory database with multiversion concurrency control). We are concerned that ignoring efficient mutexes and locks in Swiftâs concurrency model, or failing to think through the interactions, will limit Swiftâs applicability to some problems.
Thank you, again, for everyone's hard work! Please do let me know if Iâve been confusing, or if you have further questions!