Hi,
There might be an issue in the current implementation of rethrowing protocol conformance.
From what I understand, a generic type that conforms to a rethrowing protocol can rethrow only if it uses the generic parameter as a try
statement. For instance:
struct AsyncDummySequence<Base: AsyncSequence>: AsyncSequence {
typealias Element = Base.Element
typealias AsyncIterator = Iterator
let base: Base
func makeAsyncIterator() -> AsyncIterator {
Iterator(base: self.base.makeAsyncIterator())
}
struct Iterator: AsyncIteratorProtocol {
var base: Base.AsyncIterator
mutating func next() async rethrows -> Element? {
try await self.base.next()
}
}
The next()
function is allowed to rethrow because it uses the generic parameter.
let nonThrowingSequence = AsyncStream<Int> { continuation in
continuation.yield(1)
continuation.finish()
}
let throwingSequence = AsyncThrowingStream<Int, Error> { continuation in
continuation.yield(1)
continuation.finish()
}
let seq1 = AsyncDummySequence(base: nonThrowingSequence)
for await element in seq1 { // -> no need to use `try await`
print(element)
}
let seq2 = AsyncDummySequence(base: throwingSequence)
for try await element in seq2 { // -> need to use `try await`
print(element)
}
So far so good. When one want to use a try
statement not related to a generic parameter, the compiler will complain.
mutating func next() async rethrows -> Element? {
try Result<Element, Error>.failure(NSError(domain: "", code: 1)).get() // -> does not compile
}
This is the expected behaviour. But there is a way to bypass the compiler checking by wrapping the call in a rethrowing function, like withTaskCancellationHandler()
for instance (works with any rethrowing function).
mutating func next() async rethrows -> Element? {
try await withTaskCancellationHandler {
} operation: {
try Result<Element, Error>.failure(NSError(domain: "", code: 1)).get() // -> does compile
}
}
we can now misuse the AsyncDummySequence
:
let nonThrowingSequence = AsyncStream<Int> { continuation in
continuation.yield(1)
continuation.finish()
}
let seq = AsyncDummySequence(base: nonThrowingSequence)
for await element in seq { // -> no need to use `try await` although the sequence will throw
print(element)
}
When running that code, the iteration seems to be blocked.
I guess this is a known issue that is also related to a potential rethrow(unsafe)
mechanism ? I wanted to ask the question though because this is something that can already be misused in the released version of Swift.
@Douglas_Gregor or @Philippe_Hausler might be the best persons to answer that ?
Thanks a lot and have a nice day.