SE-0300 (third review): Continuations for interfacing async tasks with synchronous code

Hi Swift Evolution!

The third review of SE-0300 — Continuations for interfacing async tasks with synchronous code begins now and runs through March 9, 2021.

Following the second review of this proposal, the core team feels that the proposal is close to acceptance, and is an important feature as part of Swift's concurrency roadmap. However, a small number of amendments have been made based on feedback during the second review.

  • Replaced separate *Continuation<T> and *ThrowingContinuation<T> types with a
    single Continuation<T, E: Error> type parameterized on the error type.
  • Added a convenience resume() equivalent to resume(returning: ()) for
    continuations with a Void return type.
  • Changed with*ThrowingContinuation to take an operation block that may
    throw, and to immediately resume the task throwing the error if an uncaught
    error propagates from the operation

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 (via email or direct message in the Swift forums).

What goes into a review of a proposal?

The goal of the review process is to improve the proposal under review through constructive criticism and, eventually, determine the direction of Swift.

When reviewing a proposal, here are some questions to consider:

  • 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?

Thanks,
Ben Cohen
Review Manager

11 Likes

Looks very nice to me.

-Chris

Since the generic type T is only used for return type, should we also add the parameter returning: T.Type = T.self?


Not sure if it's handled by type checking in general, but does it work with Void closures? Or do we need to add as Void at the end? As rare as that maybe (given the targeted usage).

Very satisfied with the outcome. I can‘t wait to apply this to a lot of APIs, both Apple‘s and mine. Bring it on. Thanks!

I think the improvements make a lot of sense. One nit and one comment/question/piece of feedback:

Nit--

In the CheckedContinuation section, the function parameters for withChecked*Continuation still refer to CheckedContinuation<T> instead of CheckedContinuation<T, Never> or CheckedContinuation<T, Error> as appropriate.

Comment/question/piece of feedback--

In the previous iteration of the proposal, it somehow seemed natural to include resume(with result: Result<T, E>), but seen in a new light with the clarity afforded by consolidating the disparate types, I wonder how often this convenience will be used.

None of the examples in the proposal make use of that particular convenience; are there a lot of callback-based APIs that could take advantage of it?

If I'm not mistaken, Result does not have compiler magic for covariant generic parameters, and the with*Continuation APIs fix the type of the argument to resume(with:) to be Result<T, Never> or Result<T, Error>, and not Result<T, E>, where E is a specific error. So the convenience is restricted as it is to callback APIs that don't use Result to specify a typed error.

In the same vein, it seems that the design as-is doesn't really make use of a parameterized error type. It seems like either with*ThrowingContinuation should use a parameterized error type too, or the*Continuation types should not.

2 Likes

I haven't observed the need to explicitly state a return type in the examples I've seen, but we could certainly add this if we saw it become a problem in the wild. You should also be able to guide the type-checker's hand by writing withUnsafeContinuation { } as T to get it to pick up the return type from context.

I'll fix that, thanks.

resume(with:) came up as a result of our own experimentation with migrating some codebases to async that used Result. It's true that tying the error type of the Result to the error type of the continuation limits the usefulness of this API when the continuation has Error type; we have since added a further overload to help with this case:

extension UnsafeContinuation {
  public func resume<Er: Error>(with result: Result<T, Er>) where E == Error
}

I'll update the proposal to include this.

This is the issue with including error parameters in these APIs in general; they're more for future proofing in case we add typed errors in the future, even if today you can only really use them with Never and Error. Since we've decided to also include error type parameters in other parts of the API, like Task.Handle and Task.Group, it seems worth it to be consistent here.

4 Likes

If that is the case, then it should be fine. I just want to bring this up since the only precedence so far is Codable, which uses it (albeit they require it for a totally different reason).

PS

How does that work to add it post-release? Likely we won't be able to change its ABI. Or do you mean that we can add another overload of this?

Since the behavior would be the same as the currently proposed API, just adding an extra argument to help guide generic deduction, it could be added as an @_alwaysEmitIntoClient inlinable function, so that it doesn't expand the ABI.

2 Likes

Review Conclusion

The proposal has been accepted.

3 Likes
Terms of Service

Privacy Policy

Cookie Policy