I'd like to open the discussion on this footgun you've probably encountered or at least heard about:
Task {
for await result in myValues {
// ...
}
// Will never run: 'myValues' is infinite.
myImportantFunction()
}
I'm wondering if we could add an annotation on an (Async)Sequence to let the compiler warn us in the above example. My initial thought was a marker protocol InfiniteSequence on the (async) sequence or iterator.
The same could be done for their throwing variant:
Task {
for try await result in myValues {
// ...
}
// Will never run: 'myValues' is infinite and `try` exits the scope on failure.
myImportantFunction()
}
The same could be said about regular sequences and iterators but infinite synchronous sequences are a little rarer than their asynchronous counterparts.
Task cancellation requires the sequence to cooperate, which might not be the case. It also requires holding onto the task, which can be forgotten.
In any case with the task cancelled that important call could be async and so might exit early.
I don’t think the signature requires a change, it can remain the same for compatibility — similar to how @discardableResult removes warnings without affecting the source, we’d do the same but in reverse.
Any AsyncSequence that does not participate in cancellation imho is in violation of the expectations of that protocol; I would personally consider that a bug for that type. It is perfectly valid to delegate the behaviors to an "upstream" sequence, or it is valid to check the Task.isCancelled where appropriate. Doing nothing is odd to say the least.
The issue being listed would be a coloration problem - a map of an indefinite sequence produces an indefinite sequence.
In short; doing the design of AsyncSequence this was a topic that was brought up and the resulting decisions were that any adopter of the protocol should interoperate somehow with cancellation.
I’m not sure this is possible to define at the compile time. As has been pointed above, that’s not determined if it will never end — a lot of things can influence this, and probably all of them are runtime-bound. So warning might be false-positive quite often.
That’s a good question on how to better structure the code to avoid such cases though.
For clarity, I'm teaching Swift to children/teenagers (I want to found an Apple Swift Club) so my concern is very much about pitfalls for beginners.
I get too many "Swift is stupid, I'll learn JS, Python or C# instead" comments which aren't helped by SwiftUI (type-checker) or concurrency. The upcoming @MainActor by default should at least help with that.
I'm really just hoping to find more ways to diagnose potential issues (read: false-positives is fine by me) at build-time to reduce frustration and help with progressive disclosure. Because understanding why that task doesn't do what they think it does requires explaining too many "complex" subjects to someone that very much does not care about the details.
Asking them to always split into more Task makes things even worse because then they have to figure out how to send data from one task to the other.