Type erasure of AsyncSequences

I think I found a workaround, but kinda wordy

// Basic trick
protocol AsyncThrowingIteratorProtocol<Element>: AsyncIteratorProtocol {}

protocol AsyncNonThrowingIteratorProtocol<Element>: AsyncIteratorProtocol {
  mutating func next() async -> Element?
}

protocol AsyncSequenceOf<Element>: AsyncSequence {}

protocol AsyncNonThrowingSequenceOf<Element>: AsyncSequenceOf where Self.AsyncIterator: AsyncNonThrowingIteratorProtocol {}

typealias AsyncStreamAsyncIterator<T> = AsyncStream<T>.AsyncIterator

extension AsyncStreamAsyncIterator: AsyncNonThrowingIteratorProtocol {}

extension AsyncStream: AsyncNonThrowingSequenceOf {}

extension AsyncThrowingStream: AsyncSequenceOf {}

// Support for `.prefix(_ count:Int)`
typealias AsyncPrefixSequenceIterator<T: AsyncSequence> = AsyncPrefixSequence<T>.Iterator
extension AsyncPrefixSequenceIterator: AsyncThrowingIteratorProtocol {}
extension AsyncPrefixSequenceIterator: AsyncNonThrowingIteratorProtocol where Base: AsyncNonThrowingSequenceOf {
  mutating func next() async -> Base.Element? {
    /// Looks like a bug in the compiler. It has enough info
    /// to deduce this by itself.
    /// But for now we have to do it manually.
    /// Here we must call the implementation of
    /// `AsyncThrowingIteratorProtocol<Base.Element>.next` on `self`.
    /// But there is no syntax to express something like:
    /// `self.(AsyncThrowingIteratorProtocol.next)()`
    /// At least to my knowlendge.
    /// So instead we erase `self` to the desired protocol via opaque,
    /// call the function and apply changes made on the opaque back to `self`.
    var s: some AsyncThrowingIteratorProtocol<Base.Element> = self
    defer { self = unsafeBitCast(s, to: Self.self) }
    return try! await s.next()
  }
}
extension AsyncPrefixSequence: AsyncSequenceOf {}
extension AsyncPrefixSequence: AsyncNonThrowingSequenceOf where Base: AsyncNonThrowingSequenceOf {}

// Example
var s1: some AsyncNonThrowingSequenceOf<Int> {
  AsyncStream<Int> {
    try! await Task.sleep(nanoseconds: 1_000_000_000)
    return Int.random(in: 0...100)
  }.prefix(10)
}

var s2: some AsyncSequenceOf<Int> {
  AsyncThrowingStream<Int, Error> {
    try await Task.sleep(nanoseconds: 1_000_000_000)
    return Int.random(in: 0...100)
  }.prefix(10)
}

var it = s1.prefix(3).makeAsyncIterator()
while let i = await it.next() { print(i) } // OK

//for await i in s1.prefix(10) { } // Error: "Call can throw, but the error is not handled" :( Seems like a bug in the compiler

for try await i in s2.prefix(3) { print(i) } // OK

1 Like