Implementing AsyncZip2Sequence with async let or TaskGroup

AsyncZip2Sequence‘s AsyncIterator cannot be implemented with value semantics using async let, Task, or TaskGroup due to mutation of captured parameter self in concurrently-executing code. What am I missing? Wouldn’t it be safe to have them mutating at the same time assuming exclusive access to each (other than false sharing issues)? How is stdlib going to implement it?

Furthermore in the class-based implementation async let errors out with call can throw, but it is executed in a non-throwing autoclosure and call is to 'rethrows' function, but a conformance has a throwing witness. Try and change the tasks to async lets.

Here’s the working one.

public struct AsyncZip2Sequence<Sequence1, Sequence2>: AsyncSequence
    where Sequence1: AsyncSequence, Sequence2: AsyncSequence
{
    public typealias Element = (Sequence1.Element, Sequence2.Element)

    let sequence1: Sequence1
    let sequence2: Sequence2

    public class AsyncIterator: AsyncIteratorProtocol {
        var iterator1: Sequence1.AsyncIterator
        var iterator2: Sequence2.AsyncIterator

        init(_ _iterator1: Sequence1.AsyncIterator, _ _iterator2: Sequence2.AsyncIterator) {
            (iterator1, iterator2) = (_iterator1, _iterator2)
        }

        public func next() async throws -> Element? {
            let task1 = Task { try await iterator1.next() }
            let task2 = Task { try await iterator2.next() }
            if let value1 = try await task1.value,
               let value2 = try await task2.value {
                return (value1, value2)
            } else {
                return nil
            }
        }
    }

    public func makeAsyncIterator() -> AsyncIterator {
        AsyncIterator(sequence1.makeAsyncIterator(),
                      sequence2.makeAsyncIterator())
    }
}
Terms of Service

Privacy Policy

Cookie Policy