# \[Pitch\] Typed throws in the Concurrency module

**URL:** <https://forums.swift.org/t/pitch-typed-throws-in-the-concurrency-module/68210>\
**Category:** Pitches\
**Tags:** concurrency, typed-throws\
**Created:** [November 2, 2023, 1:53pm UTC](https://forums.swift.org/t/pitch-typed-throws-in-the-concurrency-module/68210 "2023-11-02T13:53:55Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![FranzBusch](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/franzbusch/32/19447_2.png) [@FranzBusch](https://forums.swift.org/u/FranzBusch)\
**Post date:** [November 2, 2023, 1:53pm UTC](https://forums.swift.org/t/pitch-typed-throws-in-the-concurrency-module/68210/1 "2023-11-02T13:53:55Z")

</div>

Hey everyone,

In the latest [typed throws pitch](https://forums.swift.org/t/pitch-n-1-typed-throws/67496), the adoption of typed throws in the standard library was removed due to the pitch growing quite large by itself and the large surface area of both the `Swift` and `Concurrency` module that might adopt typed throws.

This pitch covers the adoption of typed throws inside the `Concurrency` module. There are quite a few nice improvements that typed throws enable w.r.t the expressivity of task groups and tasks.

You can find the full proposal [here](https://github.com/apple/swift-evolution/pull/2197/files?short_path=358b7f5#diff-358b7f52f311c2d654aa888af748c9bc574c594b93394f0224fa6a23a084ce1f).

Looking forward to comments and questions!

Franz

---

<div class="post-metadata">

**Author:** ![Honza\_Dvorsky](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/honza_dvorsky/32/29867_2.png) [@Honza\_Dvorsky](https://forums.swift.org/u/Honza_Dvorsky)\
**Post date:** [November 2, 2023, 2:58pm UTC](https://forums.swift.org/t/pitch-typed-throws-in-the-concurrency-module/68210/2 "2023-11-02T14:58:55Z")

</div>

Hi Franz, looks great overall! A few comments below:

1. For `AsyncSequence`s, I really like that this would be coupled with adding primary associated types – I think many people are waiting for those 👍
2. Adding the `next2` method, and defaulting both `next` and `next2` to call the other unfortunately would mean that you could implement an async sequence _without_ either, and it'd compile. Furthermore, the compiler wouldn't produce errors + fixits, so it won't be as easy to just let Xcode help you fill in the missing methods. Not sure what the solution is here, without some special magic that would make `next2` the new required method, but the compiler would synthesize the `next` shim _only_ if `next2` is implemented (but not if `next2` isn't implemented, then you'd still get nice fixits and couldn't write a type without at least one `next` method).
3. Since the transformation methods on `AsyncSequence` force the same error type as the root sequence, should we add `mapError` that allows you to map the type + value of the error as well? I imagine it'd be hard to pass around async sequences in a diverse codebase without it, it's unlikely all the async sequences you want to eventually chain would share the same error type.
4. I also really like adding the `@discardableResult` attribute only on the specific Task types, definitely a nice quality-of-life improvement.

---

<div class="post-metadata">

**Author:** ![Jon\_Shier](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/jon_shier/32/921_2.png) [@Jon\_Shier](https://forums.swift.org/u/Jon_Shier)\
**Post date:** [November 2, 2023, 2:59pm UTC](https://forums.swift.org/t/pitch-typed-throws-in-the-concurrency-module/68210/3 "2023-11-02T14:59:14Z")

</div>

Not being able to backport the typed transformations is unfortunate. Is there any thing the user could do to help fill there? Would they be able to shadow the declarations locally? Anything `swift-async-algorithms` could do?

On this note:

> The newly proposed transformational `AsyncSequence` methods restrict the thrown typed of the closures to the `Failure` type of the base `AsyncSequence` . The closures could throw different error types; however, that would require error a union like type for the error.

This may deserve more exploration, as this marks `AsyncSequence`'s move into full parity with older streaming libraries. When using Combine, for example, we often used an operator built on `setFailureType(to:)` operator (such as `eraseError()`) to erase the error type in a stream. Perhaps something like that could be added? Additionally, it might be beneficial to add a `mapError` transform to allow recovery of error types from within the stream itself, as well as manual production of the union type mentioned, if the user wants. I think both of these are doable as part of this proposal. A third addition, somewhat larger, is to offer overloads of the base transforms that allow you to transform any incoming error value and type alongside the value transformation.

---

<div class="post-metadata">

**Author:** ![FranzBusch](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/franzbusch/32/19447_2.png) [@FranzBusch](https://forums.swift.org/u/FranzBusch)\
**Post date:** [November 2, 2023, 3:16pm UTC](https://forums.swift.org/t/pitch-typed-throws-in-the-concurrency-module/68210/5 "2023-11-02T15:16:30Z")

</div>

> [@Honza\_Dvorsky](#):
>
> Adding the `next2` method, and defaulting both `next` and `next2` to call the other unfortunately would mean that you could implement an async sequence _without_ either, and it'd compile. Furthermore, the compiler wouldn't produce errors + fixits, so it won't be as easy to just let Xcode help you fill in the missing methods. Not sure what the solution is here, without some special magic that would make `next2` the new required method, but the compiler would synthesize the `next` shim _only_ if `next2` is implemented (but not if `next2` isn't implemented, then you'd still get nice fixits and couldn't write a type without at least one `next` method).

Yes, that's something we have to figure out. We need a two-way default implementation here since we need existing code to bridge into the new method and the other way around as well. We have to look if there is something we can do in the compiler for this.

> [@Honza\_Dvorsky](#):
>
> Since the transformation methods on `AsyncSequence` force the same error type as the root sequence, should we add `mapError` that allows you to map the type + value of the error as well? I imagine it'd be hard to pass around async sequences in a diverse codebase without it, it's unlikely all the async sequences you want to eventually chain would share the same error type.

> [@Jon\_Shier](#):
>
> This may deserve more exploration, as this marks `AsyncSequence`'s move into full parity with older streaming libraries. When using Combine, for example, we often used an operator built on `setFailureType(to:)` operator (such as `eraseError()`) to erase the error type in a stream. Perhaps something like that could be added? Additionally, it might be beneficial to add a `mapError` transform to allow recovery of error types from within the stream itself, as well as manual production of the union type mentioned, if the user wants. I think both of these are doable as part of this proposal. A third addition, somewhat larger, is to offer overloads of the base transforms that allow you to transform any incoming error value and type alongside the value transformation.

This has come up during the discussions of this proposal and it is definitely something that is important. For now we had deferred it for the future but it might be something worth pulling into this proposal.

> [@Jon\_Shier](#):
>
> Not being able to backport the typed transformations is unfortunate. Is there any thing the user could do to help fill there? Would they be able to shadow the declarations locally? Anything `swift-async-algorithms` could do?

This is interesting but it brings up a larger problem and I personally don't want `swift-async-algorithms` to become a back deploy polypill for some of the standard library types but I understand the concern.

---

<div class="post-metadata">

**Author:** ![ksluder](https://avatars.discourse-cdn.com/v4/letter/k/eada6e/32.png) [@ksluder](https://forums.swift.org/u/ksluder)\
**Post date:** [November 2, 2023, 3:26pm UTC](https://forums.swift.org/t/pitch-typed-throws-in-the-concurrency-module/68210/6 "2023-11-02T15:26:34Z")

</div>

> `AsyncSequence` iterators can throw during iteration, as described by the `throws` on the `next()`operation on async iterators:
> 
> ```swift
> public protocol AsyncIteratorProtocol {
> associatedtype Element mutating func next() async throws -> Element?
> }
> 
> ```

Currently, untyped throws allows an AsyncIterator to throw CancellationError from `next()`. Is that now specifically disallowed?

---

<div class="post-metadata">

**Author:** ![FranzBusch](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/franzbusch/32/19447_2.png) [@FranzBusch](https://forums.swift.org/u/FranzBusch)\
**Post date:** [November 2, 2023, 3:34pm UTC](https://forums.swift.org/t/pitch-typed-throws-in-the-concurrency-module/68210/7 "2023-11-02T15:34:06Z")

</div>

> [@ksluder](#):
>
> Currently, untyped throws allows an AsyncIterator to throw CancellationError from `next()`. Is that now specifically disallowed?

The implication of typed throws on `AsyncSequence` are quite big when it comes to the throwing errors and it will require some work in the current `AsyncSequence` implementations. This is mostly a concern for the current generic source asynchronous sequences such as `AsyncStream` and `AsyncChannel`. Does should not throw a `CancellationError` and just return `nil`.

It does bring up an important point though which is that as a user you cannot differentiate any more between an `AsyncSequence` terminating normally i.e. returning `nil` or terminating due to cancellation. Since both now return `nil`. Even checking for `Task.isCancelled` after the `next()` method returned `nil` won't give you a definitive answer if the `nil` was due to cancellation since cancellation is happening from a different thread.

If it is important for users to know the difference between normal termination and task cancellation then the source asynchronous sequence should surface the `CancellationError` this can be done by either using `any Error` as the `Failure` type or by creating a use-case specific failure type.

FWIW, both `AsyncStream` and `AsyncChannel` are currently not throwing `CancellationError`s. The only places in the `Concurrency` module that throw `CancellationError`s are `checkCancellation()` and the `sleep()` methods.

---

<div class="post-metadata">

**Author:** ![Honza\_Dvorsky](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/honza_dvorsky/32/29867_2.png) [@Honza\_Dvorsky](https://forums.swift.org/u/Honza_Dvorsky)\
**Post date:** [November 2, 2023, 3:45pm UTC](https://forums.swift.org/t/pitch-typed-throws-in-the-concurrency-module/68210/8 "2023-11-02T15:45:19Z")

</div>

> [@FranzBusch](#):
>
> This has come up during the discussions of this proposal and it is definitely something that is important. For now we had deferred it for the future but it might be something worth pulling into this proposal.

Yeah I think it should happen together with adding typed throws to `AsyncSequence`, i.e. in this proposal. Without it the ergonomics of using multiple sequences with different error types get pretty bad.

---

<div class="post-metadata">

**Author:** ![ksluder](https://avatars.discourse-cdn.com/v4/letter/k/eada6e/32.png) [@ksluder](https://forums.swift.org/u/ksluder)\
**Post date:** [November 2, 2023, 3:46pm UTC](https://forums.swift.org/t/pitch-typed-throws-in-the-concurrency-module/68210/9 "2023-11-02T15:46:52Z")

</div>

> [@FranzBusch](#):
>
> If it is important for users to know the difference between normal termination and task cancellation then the source asynchronous sequence should surface the `CancellationError` this can be done by either using `any Error` as the `Failure` type or by creating a use-case specific failure type.

If you have an AsyncSequence chained to another AsyncSequence, then that intermediate sequence must also propagate the CancellationError, or else the tail sequence will not report cancellation.

It seems like this formalization is exposing a corner case of the existing API design.

I think the better alternative would be to add a `wasCancelled` property to AsyncSequence and have its iterator’s `next()` method set that flag to true when `Task.isCancelled` returns true, and/or in the case of a chained sequence, when the previous sequence’s `wasCancelled` property returns true. This would require some way for the iterator to communicate cancellation back to the sequence that outside parties could not also use.

---

<div class="post-metadata">

**Author:** ![FranzBusch](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/franzbusch/32/19447_2.png) [@FranzBusch](https://forums.swift.org/u/FranzBusch)\
**Post date:** [November 2, 2023, 3:48pm UTC](https://forums.swift.org/t/pitch-typed-throws-in-the-concurrency-module/68210/10 "2023-11-02T15:48:20Z")

</div>

> [@ksluder](#):
>
> If you have an AsyncSequence chained to another AsyncSequence, then that intermediate sequence must also propagate the CancellationError, or else the tail sequence will not report cancellation.

This propagation is mostly automatic since the transformation `AsyncSequence`s such as `map` are just calling `next` on the base. If that base is throwing a `CancellationError` they will automatically propagate it. I don't see any reason for adding `wasCancelled` to the iterator. Could you please expand on why you see a necessity for it.

---

<div class="post-metadata">

**Author:** ![ksluder](https://avatars.discourse-cdn.com/v4/letter/k/eada6e/32.png) [@ksluder](https://forums.swift.org/u/ksluder)\
**Post date:** [November 2, 2023, 3:55pm UTC](https://forums.swift.org/t/pitch-typed-throws-in-the-concurrency-module/68210/11 "2023-11-02T15:55:57Z")

</div>

Because once `next()` becomes typed, all sequences in the chain must have `typealias Failure = any Error` or else any one of them will swallow the cancellation and turn it into `nil`.

The fact that this happens today is an artifact of AsyncStream’s implementation, but with this change it would become enforced by the type system.

---

<div class="post-metadata">

**Author:** ![FranzBusch](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/franzbusch/32/19447_2.png) [@FranzBusch](https://forums.swift.org/u/FranzBusch)\
**Post date:** [November 2, 2023, 3:59pm UTC](https://forums.swift.org/t/pitch-typed-throws-in-the-concurrency-module/68210/12 "2023-11-02T15:59:42Z")

</div>

> [@ksluder](#):
>
> Because once `next()` becomes typed, all sequences in the chain must have `typealias Failure = any Error` or else any one of them will swallow the cancellation and turn it into `nil`.

Most transformative `AsyncSequence`s are just going to forward the error that they receive from their base e.g. their `Failure` type will be constrained to `Base.Failure`. Which means they will just forward `CancellationError`s. Now there is a set of transformative `AsyncSequence`s that do change the error e.g. a potential `mapError`. Those need to decide what they do if they get a `CancellationError` and for probably all of them this is up to the user.

I don't expect that we would have any `AsyncSequence` in either the `Concurrency` module nor `swift-async-algorithms` that would just swallow `CancellationError`s.

---

<div class="post-metadata">

**Author:** ![Philippe\_Hausler](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/philippe_hausler/32/22569_2.png) [@Philippe\_Hausler](https://forums.swift.org/u/Philippe_Hausler)\
**Post date:** [November 2, 2023, 4:00pm UTC](https://forums.swift.org/t/pitch-typed-throws-in-the-concurrency-module/68210/13 "2023-11-02T16:00:08Z")

</div>

> [@ksluder](#):
>
> The fact that this happens today is an artifact of AsyncStream’s implementation, but with this change it would become enforced by the type system.

I don't think there are any explicitly throwing `CancellationError` instances in `AsyncStream` or even `AsyncThrowingStream` today.

---

<div class="post-metadata">

**Author:** ![ksluder](https://avatars.discourse-cdn.com/v4/letter/k/eada6e/32.png) [@ksluder](https://forums.swift.org/u/ksluder)\
**Post date:** [November 2, 2023, 4:26pm UTC](https://forums.swift.org/t/pitch-typed-throws-in-the-concurrency-module/68210/14 "2023-11-02T16:26:29Z")

</div>

On a completely different topic: if AsyncThrowingStream is subsuming the role of AsyncStream, is it possible to hide the old AsyncStream and replace it with AsyncThrowingStream in the surface language? Something like:

```swift
#if $TypedThrows
typealias AsyncStream = AsyncThrowingStream where Failure == Never

@available(*, unavailable)
protocol AsyncStream {
  …
}

```

---

<div class="post-metadata">

**Author:** ![FranzBusch](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/franzbusch/32/19447_2.png) [@FranzBusch](https://forums.swift.org/u/FranzBusch)\
**Post date:** [November 2, 2023, 6:01pm UTC](https://forums.swift.org/t/pitch-typed-throws-in-the-concurrency-module/68210/15 "2023-11-02T18:01:30Z")

</div>

> [@ksluder](#):
>
> On a completely different topic: if AsyncThrowingStream is subsuming the role of AsyncStream, is it possible to hide the old AsyncStream and replace it with AsyncThrowingStream in the surface language?

We can’t drop the current `AsyncStream` type since it is already baked into API and ABI. Furthermore, it also has a different signature than `AsyncThrowingStream` which has two generic parameters. What we can probably do is deprecate `AsyncStream` in the long run. I personally think we should do this in stages and not with this pitch.

---

<div class="post-metadata">

**Author:** ![ksluder](https://avatars.discourse-cdn.com/v4/letter/k/eada6e/32.png) [@ksluder](https://forums.swift.org/u/ksluder)\
**Post date:** [November 2, 2023, 6:03pm UTC](https://forums.swift.org/t/pitch-typed-throws-in-the-concurrency-module/68210/16 "2023-11-02T18:03:53Z")

</div>

> [@FranzBusch](#):
>
> We can[’t] drop the current `AsyncStream` type since it is already baked into API and ABI.

That’s why I’m suggesting marking it unavailable (and I guess deprecated) and providing a type alias. That should only be visible to the client without affecting the ABI.

---

<div class="post-metadata">

**Author:** ![wadetregaskis](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/wadetregaskis/32/8494_2.png) [@wadetregaskis](https://forums.swift.org/u/wadetregaskis)\
**Post date:** [November 2, 2023, 10:53pm UTC](https://forums.swift.org/t/pitch-typed-throws-in-the-concurrency-module/68210/17 "2023-11-02T22:53:31Z")

</div>

> [@FranzBusch](#):
>
> We can drop the current `AsyncStream` type since it is already baked into API and ABI.

_Can_?

---

<div class="post-metadata">

**Author:** ![FranzBusch](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/franzbusch/32/19447_2.png) [@FranzBusch](https://forums.swift.org/u/FranzBusch)\
**Post date:** [November 2, 2023, 11:18pm UTC](https://forums.swift.org/t/pitch-typed-throws-in-the-concurrency-module/68210/18 "2023-11-02T23:18:03Z")

</div>

> [@wadetregaskis](#):
>
> _Can_?

Thanks for the call out. I meant to write can’t.

---

<div class="post-metadata">

**Author:** ![KeithBauerANZ](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/keithbaueranz/32/27897_2.png) [@KeithBauerANZ](https://forums.swift.org/u/KeithBauerANZ)\
**Post date:** [November 9, 2023, 5:55am UTC](https://forums.swift.org/t/pitch-typed-throws-in-the-concurrency-module/68210/19 "2023-11-09T05:55:23Z")

</div>

It's not obvious to me, and I didn't see it discussed in the proposal, why we need `next2()` on `AsyncIteratorProtocol`? Why is adding the typed throw incompatible with the previous `@rethrows` behavior?

---

<div class="post-metadata">

**Author:** ![ktoso](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/ktoso/32/44526_2.png) [@ktoso](https://forums.swift.org/u/ktoso)\
**Post date:** [November 9, 2023, 6:01am UTC](https://forums.swift.org/t/pitch-typed-throws-in-the-concurrency-module/68210/20 "2023-11-09T06:01:15Z")

</div>

> [@Honza\_Dvorsky](#):
>
> Adding the `next2` method, and defaulting both `next` and `next2` to call the other unfortunately would mean that you could implement an async sequence _without_ either, and it'd compile. Furthermore, the compiler wouldn't produce errors + fixits

It would because we do handle such situations already in the compiler. The Executor/enqueue method is a recent example of that, and Hashable also. The introduction of a new next method can be made pretty seamless if we needed it to be. I.e. deprecating the old and recommending the new etc.

---

<div class="post-metadata">

**Author:** ![Honza\_Dvorsky](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/honza_dvorsky/32/29867_2.png) [@Honza\_Dvorsky](https://forums.swift.org/u/Honza_Dvorsky)\
**Post date:** [November 9, 2023, 6:24am UTC](https://forums.swift.org/t/pitch-typed-throws-in-the-concurrency-module/68210/21 "2023-11-09T06:24:16Z")

</div>

@ktoso Nice! Is that special cased in the compiler for each type, or is that another use of `@_disfavoredOverload`?

[Next page](https://forums.swift.org/t/pitch-typed-throws-in-the-concurrency-module/68210.md?page=2)
