# \[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:** 1\
**Showing post:** 28

<div class="post-metadata">

**Author:** ![Douglas\_Gregor](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/douglas_gregor/32/1362_2.png) [@Douglas\_Gregor](https://forums.swift.org/u/Douglas_Gregor)\
**Post date:** [January 2, 2024, 10:02pm UTC](https://forums.swift.org/t/pitch-typed-throws-in-the-concurrency-module/68210/28 "2024-01-02T22:02:32Z")

</div>

Hey all,

I took a stab at implementing the `AsyncSequence` part of this protocol, to assess whether it's possible to introduce typed throws in a manner that is both backward compatible and achieves the composability we want, with support for `any AsyncSequence<Element, Failure>` and such. The implementation in the compiler and library is in [this pull request](https://github.com/apple/swift/pull/70635), along with toolchains to play with, but the salient details are below.

**tl;dr** we can stage in the new `Failure` associated type without breaking existing code, and the unfinished/unofficial `@rethrows` can be removed over time.

With my implementation, `AsyncSequence` and `AsyncIteratorProtocol` both get `Failure` associated types and adopt primary associated types, as in the proposal. `AsyncIteratorProtocol` gets a new function requirement `_nextElement()` that is a typed-throws version of `next()`:

```swift
protocol AsyncIteratorProtocol<Element, Failure> {
  associatedtype Element
  associatedtype Failure: Error = any Error
  mutating func next() async throws -> Element?
  mutating func _nextElement() async throws(Failure) -> Element?
}

public protocol AsyncSequence<Element, Failure> {
  associatedtype AsyncIterator: AsyncIteratorProtocol
  associatedtype Element where AsyncIterator.Element == Element
  associatedtype Failure = AsyncIterator.Failure where AsyncIterator.Failure == Failure
  func makeAsyncIterator() -> AsyncIterator
}

```

Because existing `AsyncIteratorProtocol`-conforming types only implement `next()`, we need to provide a default implementation of `_nextElement`:

```swift
extension AsyncIteratorProtocol {
  /// Default implementation of `_nextElement()` in terms of `next()`, which is
  /// required to maintain backward compatibility with existing async iterators.
  public mutating func _nextElement() async throws(Failure) -> Element? {
    do {
      return try await next()
    } catch {
      throw error as! Failure
    }
  }
}

```

I didn't also implement `next()` in terms of `_nextElement()`, but we'd want to do that so that new async sequences could implement just `_nextElement()`.

Now, one of the harder problems is how to get the right `Failure` type for existing async sequences. If the async sequence doesn't get recompiled, it'll get the default `Failure` type of `any Error` at runtime. This is fine---either it doesn't throw anything in practice, or its clients will see the `any Error` instance.

When the async sequence does get recompiled, we want to pick an appropriate `Failure` type even when there is no explicitly-specified one. I ended up using the following inference logic based on the `next()` implementation:

- If `next()` throws nothing, `Failure` is inferred to `Never`.
- If `next()` throws, `Failure` is inferred to `any Error`.
- If `next()` rethrows, `Failure` is inferred to `T.Failure`, where `T` is the first type parameter with a conformance to either `AsyncSequence` or `AsyncIteratorProtocol`. If there are multiple such requirements, take the `errorUnion` of them all.

The async `for..in` loop switches from using `next()` to using `_nextElement()`, so iteration over an async sequence throws its `Failure` type. This subsumes the specialized behavior for `@rethrows` (if `Failure` is `Never`, you don't need the `try` because nothing is thrown), and gives us typed-throws behavior for iteration.

`@rethrows` protocols had another bit of special behavior, which is that conformance requirements to `@rethrows` protocols can be considered as sources of errors for rethrowing. So, you can currently write a `rethrows` function like this:

```swift
extension AsyncSequence {
  func contains(_ predicate: (Element) async throws -> Bool) rethrows -> Bool { ... }
}

```

and this function can throw if either the `AsyncSequence` throws (i.e., it's `Failure` type is not `Never`) or if the `predicate` throws. @pyrtsa [noted this issue](https://forums.swift.org/t/pitch-typed-throws-in-the-concurrency-module/68210/27). I've partially addressed the problem by introducing a specific rule that allows requirements on `AsyncSequence` and `AsyncIteratorProtocol` to be involved in `rethrows` checking, so existing code that uses `rethrows` in this manner with async sequences will continue to work.

However, that doesn't address the fact that we can't write a proper typed throws signature for `contains`. As @pyrtsa noted, we could elevate `errorUnion` to an actual type in the type system, so we could write, e.g.,:

```swift
extension AsyncSequence {
  func contains<E: Error>(_ predicate: (Element) async throws(E) -> Bool) throws(errorUnion(Failure,E)) -> Bool { ... }
}

```

that's effectively what I've turned `rethrows` into, implicitly. We'd need to do something like this to fully replace the current `rethrows` behavior.

Doug

---

_[View the full topic](https://forums.swift.org/t/pitch-typed-throws-in-the-concurrency-module/68210)._
