Blog series: generators

I'd say that the same reasoning for async/Future applies here: borrowing over yield points. It may be less prevalent in desire, but that may also be because we tend to handle the owned state between yielded items more easily (due to practice?) for yield and it is less important for functionality than await.

Anytime I find myself building a complex state machine from simple rules, and don't rely on the exact shape of said state machine, I find myself wondering why the compiler can't handle it for me (and potentially spot tricky space optimization I'd not find myself).

I think the real root problem here is that using impl Iterator<Item=Result<T, E>> to represent iterators that may fail rather than iterators over a sequence of Result s is a lossy and awkward pattern to begin with.

I'm sure similar logic was used to decide to have a separate Error associated type in futures 0.1, but that proved to result in huge usability issues, hence why the form being stabilized does away with it.

Fallible iterators are free to return either a Result<Result<T, E>, E> or a Result<T, E> (with an E enum with a variant for the iterator having failed). The former is closer to what you seem to be proposing, but I would much prefer the latter in any API I'm going to use.

As a long-time advocate of removing Error from Future, I disagree that the situations are analogous. A future that yields Result<T, E> is unambiguous; there is no question of whether it is in any sense “valid” afterwards, because Futures are always complete once they yield a value. No information is lost. In fact, the original motivation for including an Error case was for improved ergonomics.

By contrast, an Iterator over Result<T, E> is often an Iterator over a series of Results which may legitimately contain multiple Err values of interest; assigning a special meaning to Err in some cases is therefore a fragile pattern, as important semantic information is erased from the type.

I am not proposing anybody write Iterator<Item=Result<Result<T, E>, F>>, as that has exactly the same problem. The pre-RFC I cited outlines approaches to providing good ergonomics for fallible generators.

4 Likes

Can you link the pre-RFC? I'm having trouble finding it.

Without seeing the details, I still think a single output is preferable. It is much more ergonomic to handle all cases (success, generator failure, error result) in a single match expression. With Future v0.1, I've often found myself creating my own error enums just to flatten all error cases so that I can apply a combinator. Why make the API consumer define the error enum when the API author can provide it once for everyone?

Edit:

assigning a special meaning to Err in some cases is therefore a fragile pattern, as important semantic information is erased from the type

The only information that could be lost is whether or not the iterator is invalid for future calls to next. This is certainly important information, but it's not clear that all implementors of Iterator would want invalidation to behave the same way. Some implementors could easily keep returning the error, while others might need to panic after the first error. Why enforce a particular failure behavior on all fallible iterators, when their custom error type can easily document the correct semantics in each case?

Just look earlier in this topic.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.