Essential combinators
We saw a few of the most important combinators in the futures and streams overviews. Here we’ll take a look at a few more. It’s also worth spending some time with the trait documentation to familiarize yourself with the full range of combinators available (cheatsheet).
Some concrete futures and streams
Any value can be turned into an immediately complete future. There are a few
functions in the future
module for creating such a future:
ok
, which is analogous toResult::Ok
: it treats the value you give it as an immediately successful future.err
, which is analogous toResult::Err
: it treats the value you give it as an immediately failed future.result
, which lifts a result to an immediately-complete future.
For streams, there are a few equivalents of an “immediately ready” stream:
iter
, which creates a stream that yields the same items as the underlying iterator. The iterator producesResult
values, and the first error terminates the stream with that error.once
, which creates a single-element stream from aResult
.
In addition to these constructors, there’s also a function, lazy
, which
allows you to construct a future given a closure that will produce that future
later, on demand.
IntoFuture
A crucial API to know about is the IntoFuture
trait, which is a trait for
values that can be converted into futures. Most APIs that you think of as taking
futures actually work with this trait instead. The key reason: the trait is
implemented for Result
, allowing you to return Result
values in many places
that futures are expected.
Adapters
Like Iterator
, the Future
, Stream
and Sink
traits all come equipped
with a broad range of “adapter” methods. These methods all consume the receiving
object and return a new, wrapped one. For futures, you can use adapters to:
- Change the type of a future (
map
,map_err
) - Run another future after one has completed (
then
,and_then
,or_else
) - Figure out which of two futures resolves first (
select
) - Wait for two futures to both complete (
join
) - Convert to a trait object (
Box::new
) - Convert unwinding into errors (
catch_unwind
)
For streams, there are a large set of adapters, including:
- Many in common with
Iterator
, likemap
,fold
,collect
,filter
,zip
,take
,skip
and so on. Note thatfold
andcollect
produce futures, and hence their result is computed asynchronously. - Adapters for sequencing with futures (
then
,and_then
,or_else
) - Additional adapters for combining streams (
merge
,select
)
The Sink
trait currently has fewer adapters
Finally, an object that is both a stream and a sink can be broken into separate
stream and sink objects using the split
adapter.
All adapters are zero-cost, meaning that no memory is allocated internally and the implementation will optimize to what you would have otherwise written by hand.
Error handling
Futures, streams and sinks all treat error handling as a core concern: they are all equipped with an associated error type, and the various adapter methods interpret errors in sensible ways. For example:
The sequencing combinators
then
,and_then
,or_else
,map
, andmap_err
all chain errors similarly to theResult
type in the standard library. So, for example, if you chain futures usingand_then
and the first future fails with an error, the chained future is never run.Combinators like
select
andjoin
also deal with errors. Forselect
, the first future to complete in any way yields an answer, propagating the error, but also giving access to the other future should you want to keep working with it. Forjoin
, if any future produces an error, the entire join produces that error.
By default, futures don’t have any special handling for panics. In most cases,
though, futures are ultimately run as tasks within a thread pool, where you’ll
want to catch any panic they produce and propagate that elsewhere. The
catch_unwind
adapter can be used to reify a panic into a Result
without
taking down the worker thread.