Can you catch exceptions? Can you catch them when thrown iterating an Iterable, awaiting a Future, or iteratively awaiting values from a Stream?
It doesn't seem like you can get around needing fallibility. What if I have an asynchronous operation that can fail? What if I need a sequence of possibly-empty values?
You can catch exceptions. If an iterator throws, you can catch that by surrounding the for loop in a try catch block.
Futures are sort of like Result types in that a Future can complete with either a value or an error. Exceptions inside asynchronous functions are automatically caught and wrapped into error futures, which then propagate out through the returned future object. When you await a future, if the future completes with an error, that then gets unwrapped back into a thrown exception.
Incidentally, for curious readers, the first version of the Future trait in Rust always returned a Result<T, E> from its poll method. The second version decided to instead return T directly, since T could be Result<T, E> too, making things a bit more general, but also leading to a tiny bit of type tetris.
Doesn't mean that I'm saying anything about whatever Dart does here, just that I also think there's a connection here :)
There’s no axis for fallability because all operations are fallible. Yes, you can catch exceptions when iterating, awaiting, or iteratively awaiting. You have fallibility. What you don’t have is a way of marking operations as non-fallible.
Still waiting for the day that HN supports Markdown for inline code. :(
\*
**
Either one works to get an * into your non-code block text.
When Erik Meijer worked with Gilad Bracha and others on the Dart team to bring async to Dart, he approached with a very similar "lets look at the table of all the combinations and make sure we didn't leave any holes". In this case, the two axes (effects) he considered were asynchrony and iteration. So Dart supports all four combinations:
* Synchronous single-value functions are just normal function declarations. Within one, you use `return` to yield the one value.
* Synchronous multi-value functions are generators, marked `sync`, which return a `Iterable<T>`. Within one, you use `yield` and `yield` to emit values.
* Asynchronous single-value functions are marked `async` and return a `Future<T>`. Within on, you use `await` to pause on asynchronous operations and consume values. A `return` valued is implicitly wrapped in a future.
* Asynchronous multi-value functions are marked `async` and return a `Stream<T>`. Within one, you can use both `yield` and `yield` to emit values and `await` to suspend and consume asynchronous values.
We also have an asynchronous `await for` statement that can be used to imperatively consume a stream.
There's no third axis for fallibility, because Dart chooses to comprehensively use exceptions for operations that fail (which I quite like).
Overall, it's a really rich, comprehensive approach to the problem. I have mixed feelings about it. It really does cover all the bases. But, in practice, `async` functions and `await for` are very* rarely used but add a lot of complexity to the language and engineering burden to the implementations.