A peek at
Futures & Streams
in

by @holsee

Futures

Streams
Event Loops

Futures

Streams
Event Loops

Futures

Futures are a concept for an object which is a proxy for another value that may not be ready yet.

 

With a common mechanism for handling these "maybe not ready yet" values we are able to compose futures together which are performing very different operation types to obtain their values and ultimately may or may not be successful.

A future in Rust is anything that implements the Future trait

trait Future {
    // The type of value that the future yields on 
    // successful completion.
    type Item;

    // The type of value that the future yields on failure.
    type Error;

    // The only required method, which attempts to 
    // complete the future.
    fn poll(&mut self) -> Poll<Self::Item, Self::Error>;

    // Blocks until completion.
    fn wait(self) -> Result<Self::Item, Self::Error> { ... }

    // Transforms the result of the future using the given closure.
    fn map<F, U>(self, f: F) -> Map<Self, F>
        where F: FnOnce(Self::Item) -> U { ... }

    // ... and many, many more provided methods
}

It has a lot of similarities to the Iterator trait in the standard library.

Futures can be used for...

A database query that’s executing in a thread pool.

When the query finishes, the future is completed, and its value is the result of the query.

An RPC invocation to a server. 

When the server replies, the future is completed, and its value is the server’s response.

A timeout.

When time is up, the future is completed, and its value is ().

A long-running CPU-intensive task, running on a thread pool. 

 

When the task finishes, the future is completed, and its value is the return value of the task.

Handling Aync IO Requests, running on an event loop. 

 

... this is a good one for Streams so we will leave this for then.

Example: Futures over CPU Thread Pool with Timeout

// set up a thread pool
let pool = CpuPool::new_num_cpus();

// spawn our computation, getting back a *future* of the answer
let prime_future = pool.spawn_fn(|| {
    // the trait `futures::Future` is not implemented for `bool`
    // so we return a Result (think the Option type in Rust)
    Ok(is_prime(BIG_PRIME))
});

println!("Created the future");

// unwrap here since we know the result is Ok
if prime_future.wait().unwrap() {
    println!("Prime");
} else {
    println!("Not prime");
}

Futures over Thread Pool

let pool = CpuPool::new(4);
let timer = Timer::default();

// a future that resolves to Err after a timeout
let timeout = timer.sleep(Duration::from_millis(1500))
                   .then(|_| Err(()));

let fprime = pool.spawn_fn(|| -> Result<bool, ()> {
    Ok(is_prime(BIG_PRIME))
});

// a future that resolves to one of the above values -- whichever
// completes first!
let winner = timeout.select(fprime)
                    .map(|(win, _)| win);

// now block until we have a winner, then print what happened
match winner.wait() {
    Ok(true) => println!("Prime"),
    Ok(false) => println!("Not prime"),
    Err(_) => println!("Timed out"),
}

...composed with a Timeout

Here, we’re using a couple of additional methods on futures:

then, which in general allows you to sequence one future to run after getting the value of another.

// a future that resolves to Err after a timeout
let timeout = timer.sleep(Duration::from_millis(1500))
                   .then(|_| Err(()));

In this case, we’re just using it to change the value returned from the timeout future to Err(()).

Here, we’re using a couple of additional methods on futures:

 

select, which combines two futures of the same type, allowing them to “race” to completion.

// a future that resolves to one of the above values -- whichever
// completes first!
let winner = timeout.select(fprime)
                    .map(|(win, _)| win);

 It yields a pair, where the first component is the value produced by the first future to complete, and the second gives you the other future back. Here, we just take the winning value.

Wrapping up on Futures...

While this example is simplistic, it gives some sense for how futures scale up.

 

Once you have a number of basic “events” set up as futures, you can combine them in complex ways, and the futures library takes care of tracking all of the relevant state and synchronisation.

 

For example behind the scenes managing concurrent execution of is_prime on a thread pool, the timer thread managed by Timer, and the main thread calling wait all at once.

Streams

A stream here is a sequential sequence of values which may take some amount of time in between to produce.

A stream may request that it is blocked between values while the next value is calculated, and provides a way to get notified once the next value is ready as well.

Let’s see how Future and Stream relate to their synchronous equivalents in the standard library:

 

items           Sync          Async          Common operations

1 Result Future map, and_then
Iterator Stream map, fold, collect

 

 

A stream in Rust is anything that implements the Stream trait

The definition of the Stream trait also resembles that of Iterator:

trait Stream {
    // The type of item yielded each time the stream's event occurs
    type Item;

    // The error type; errors terminate the stream.
    type Error;

    // Try to produce a value.
    fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error>;

    // ... and many default methods...
}

Thanks!

Streams and Futures in Rust

By Steven Holdsworth

Streams and Futures in Rust

Zero Cost Abstractions

  • 305