On the tokio chat, @carllerche suggested that one way to simplify the "errors on streams" problem (rust-lang/futures-rs#206) would be to remove the error alternative from Future
& Stream
. This idea stuck with me, and I want to sketch out a possible alternative, to see how it would look.
Future<Item, Error>
: A future resolves to either an item or an error type, exactly like an async version ofResult
.poll() -> Poll<Item, Error>
: not ready, or an item, or an error
Stream<Item, Error>
: A stream emits elements that are each either an item or error, exactly like an async iterable ofResult
.poll() -> Poll<Option<Item>, Error>
: not ready, or the end of the stream (Ready(None)
), or an item, or an error
Most streams will be "dead" once they emit an error, so many combinators assume that an error item is also the final possible emitted element, hence the above bug.
Future<Item>
: A future is an async value. If the value could fail, the item is aResult
. (Future<Result<T, Error>>
)poll() -> Poll<Item>
: not ready, or a value
Stream<Item>
: A stream is an async iterator, which just emits values until it's done.Stream<Result<T, Error>>
mimics the old behavior.poll() -> Poll<Option<Item>>
: not ready, or end of the stream, or an item
I don't think the streams from alternative 1 are very commom in the wild. Instead, I think they often emit values until they end successfully or encounter an error. So maybe a stream is an async iterator that emits values until it ends, with a Result
representing final success or failure.
Stream<Item, T>
: A stream is an async iterator, emitting values until ending with a final resultT
. If the stream can end in error,T
should be aResult
.Stream<Item, Result<(), Error>>
is the network case: items arrive until the stream ends or encounters an error.Stream<Item, Result<T, Error>>
is a special case where the completed stream has some final value, or would like to return some resource that it used to process the stream (like a socket).
The collect()
method would instead return Future<(Iterator<Item>, T)>
. It's unwieldly but would allow a process to handle errors.
The into_future()
method would similarly need to return a future of an enum: a possible next value, or the final stream result: Future<...>
where enum is either NextItem(Item, Stream)
or Done(T)
This alternative is more complicated, so I'm not sure how I feel about it. But it does solve the problem that streams usually end because they succeeded or failed, which isn't handled in alternative 1.
Thanks for this, interesting read.
Just thinking about potential ugliness - in Alternative 2, a
Stream
which can non-fatally error would be:Stream<Result<Item, Error>, ()>
. Not too bad compared to the existing situation?Stream<Result<Item, Error>, Result<(), Error>>
. Hmm.Stream<Result<Item, Error>, Result<T, Error>>
. Extreme case, maybe a bit hard to reason about from the type.I would like if a
Stream
could have a specified end value, as an async equivalent ofinto_inner()
. I'm not clear what the return values would be for a stream encountering an error and returning a final value?