by @holsee
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.
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.
// 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");
}
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"),
}
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(()).
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.
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.
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.
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...
}