[Accepted with Modification] SE-0296: async/await

The review of SE-0296 – async/await has concluded and the proposal is accepted.

Feedback was very positive on the concept of adding async/await in general with a few key points raised:

  • It was suggested that try await reads better than await try. The core team agrees, and the proposal will be modified accordingly.
  • There was some discussion of alternatives to async (such as suspends) which may better describe the meaning. The core team feels that the benefit of sticking with async as a term of art with precedent in many other languages was preferable to the the slight descriptive benefit of alternative names. Note that other uses of async such as async let will be considered in other proposals.
  • Several reviewers expressed concern that it was hard to review this proposal "stand alone", since it interacts so closely with, and its use depends on, other yet-to-be-reviewed proposals. The core team acknowledges this, but feels that this is unavoidable given the large surface area of the whole concurrency feature. To mitigate this, reviewers of subsequent proposals should feel free to revisit parts of accepted concurrency proposals in reviewing those subsequent proposals when they interact.
  • Several reviewers were disappointed about subsetting out getters. The core team wants to be clear that this is just left as a future direction, not ruled out, and as such isn't a reason to hold back on accepting this proposal.
  • In a separate thread to the review, there was some discussion of the necessity of try and await. The core team does not believe the current requirement to mark throwing calls with try should be revisited, and thinks there is a similar need to mark possible suspension points with await. The core team would be open to considering future proposals that allow multiple calls needing either try or await to be sugared somehow (for example, some form of try block).

This is an important first step on our concurrency journey. To help navigate how this proposal relates to expected future proposals, this diagram should be of help:

Thanks to everyone who participated in the review!

Ben Cohen
Review Manager

81 Likes

I thought that @Douglas_Gregor had agreed to remove the restriction on using await in defer blocks. At least this is what he said:

So let's chalk this up to "we should remove this restriction unless more arguments come up in favor of keeping it."

Was there a compelling reason to keep that restriction, or was this an oversight?

1 Like

The Core Team felt that implicitly suspending at break/continue/return/throw/fall-off-the-end was too subtle. They/we are open to lifting this restriction in the future, but there should be more discussion around making this less subtle.

Doug

5 Likes

It is still not clear to me whether the proposal includes a way for async functions to directly give up their thread without awaiting another async function.

In the proposed solution section, the proposal text states:

think of an asynchronous function as an ordinary function that has the special power to give up its thread. Asynchronous functions don’t typically use this power directly;

By saying async functions don’t “typically” give up their thread directly, the reader is left with the impression that they can do so.

Is that in fact part of the proposal?

No, this is Task.yield() in the structured concurrency proposal.

Doug

2 Likes

Thanks

This certainly sounds more natural. For consistency's sake, I hope function signatures will be changed from async throws to throws async —is this correct? I imagine that it will be hard to keep track of which keyword comes first otherwise.

On the other hand, that leaves async let in maybe an odd position...

1 Like

You could think of it as inner-to-outer, because the awaiting happens before the throwing, in which case the ordering makes sense.

11 Likes

You can remember it because it's mirrored:

func f() async throws -> String
//   (1) (2)   (3)   (4) (5)
let string: String = try await f()
//          (5)   (4)(3) (2)   (1)
41 Likes

You may also consider Typed throws (WIP), by which having async throws ErrorType may be more eye-pleasing than throws ErrorType async.

5 Likes

These all sound like good reasons. Thanks for the clarification!

1 Like

This is great, just one question:
The fact that the proposal is "accepted" means that this will be included in swift 5.4 already?

It does not mean that.

1 Like

Unlikely. My prediction is Swift 6 @ WWDC.

1 Like

Swift 6 is very far away, there're at least 5.4-spring and 5.5-fall releases in 2021 between next milestone maybe 2022 for swift 6. I think concurrency feature set phase1 will be part of 5.4 and phase 2 finished in 5.5.

1 Like

Let's not speculate about the release time of new features, even if they are in sight.

14 Likes

I hope it won't. That would mean it is declared stable and can't be changed anymore before we are able to test the whole stack more widely.

For such feature, the more time we got before freezing it, the better.

8 Likes

Something I'm missing in this whole thing is just what the internals of the async function look like. I see a lot of examples of calling async functions using await, but I didn't see how the function did something internally (perhaps calling a network API) and then returned a value.

func loadWebResource() async -> Data {
    makeAPICall { result in
        // how to return function value here??? Is it as simple as...
        return result
    }
}

Or to put it another way, how do I write my own async functions?

I believe the details of that will be worked out in the structured concurrency proposal, which hasn't yet been put up for review. But currently you can implement async functions using the withUnsafeContinuation and withUnsafeThrowingContinuation apis.

Here's an example for performing a network request that works on the latest Swift toolchain:

struct RequestError: Error {
    var error: Error?
    var response: URLResponse?
}

func request(_ request: URLRequest, session: URLSession = .shared) async throws -> (Data, URLResponse) {
    await try withUnsafeThrowingContinuation { continuation in
        let task = session.dataTask(with: request) { data, response, error in
            if let data = data, let response = response {
                continuation.resume(returning: (data, response))
            } else {
                continuation.resume(throwing: RequestError(error: error, response: response))
            }

        }
        task.resume()
    }
}
2 Likes

This question has come up consistently a sufficient number of times that I would suggest that the authors might consider incorporating text into the proposal to address it, even if it is just pointing to a future proposal. It seems to be key to user understanding of what's being proposed.

8 Likes