futures-rs: Consider having polling an error represent the final Stream value

Consider having polling an error represent the final value. In other words, a poll that returns an error means that poll should never be called again. In the case of a Stream, this means that an error indicates the stream has terminated.

This issue is a placeholder for the associated discussion.

cc @aturon

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Comments: 57 (36 by maintainers)

Most upvoted comments

IMO this is an argument in favor of having the Stream error terminate the stream.

There are two different error classes with streams:

  1. A stream of values that are either OK or errors.
  2. An error in the production of values, which means that no further values may be produced due to that error.

The current behavior of stream errors implies the first category which makes the second class of errors difficult to model.

If, however, the stream error terminates the stream, this implies that a stream error follows under group #2.

TcpListener::incoming is group #1, in which case, I believe this is modeled much better with a Stream<Item = Result<T, E>, E2>

This lets you differentiate between producing an error value, and an error in value production (you can have potentially two different error types).

As pointed out, combinators like or_else, and_then, then, etc… don’t make sense anymore, but a different set of combinators do:

incoming
  .map(my_or_else_fn)
  .take_while(Result::is_ok)

For example would provide similar behavior as the current or_else

(the resolution was to not change the behaviour - a stream returning an error is not terminated and can yield further items or errors)

Hi, just my $0.02. I’m new to Rust, but I’ve used to work with RxScala and a little bit with another Scala library, Monix (better designed IMO) in the past. Both strive to be compatible with the Reactive Streams spec. I’m not really sure how similar futures streams are or want to be to the Reactive Streams spec, but the spec considers the reactive stream done with after encountering an error, no more items are emitted after an error, so I’m used to this behavior.

I’m not sure if this is relevant to the discussion as I’m VERY new to Rust and futures streams. Still, I wanted to mention Reactive Streams in case somebody finds it useful.

@carllerche why? However short you comment is, I cannot read your mind, and I’d love you to share an experience, that made you change your mind.

I tend to think that it’s much more common for stream errors to be “fatal” than recoverable, so I agree with @carllerche that we’ve probably chosen the wrong default here.

Very often chain stream.op1(...).op2(...).op3(...) will have tiny and simple functions/closures that go into opN(...). Above else it makes code simple to read. 🥇 But this also means that a closure in op1 is not equipped to deal with, say 3, possible conditions besides a normal event. However trivial those runtime conditions can be on a big scale, from a point of view of a small section, their are fatal. Of cause, on a big scale, i.e. on the level of constructing process shaping operations, developer just adds appropriate catch and retry, where desired.

I the world with streams terminating on an error, would there still be a place for combinators that recover from errors like or_else and then?

They could recover from one error, but what next? Their only option seems to be to terminate the stream, as otherwise they would be breaking the Stream contract. Notice that it is the combinator that would be breaking the contract on continued polling, not a downstream consumer.

This seems to be a weak point of stopping on errors. Error recovery is possible, but only once. Alternatively, those combinators could have stronger preconditions to work only with streams that permit further polling on errors.