Futures
As a quick review, let’s take a very basic asynchronous function. This is nothing new compared to what the tutorial has covered so far.
use tokio::net::TcpStream;
async fn my_async_fn() {
println!("hello from async");
let _socket = TcpStream::connect("127.0.0.1:3000").await.unwrap();
println!("async TCP operation complete");
}
We call the function and it returns some value. We call .await
on that value.
#[tokio::main]
async fn main() {
let what_is_this = my_async_fn();
// Nothing has been printed yet.
what_is_this.await;
// Text has been printed and socket has been
// established and closed.
}
The value returned by my_async_fn()
is a future. A future is a value that implements the std::future::Future
trait provided by the standard library. They are values that contain the in-progress asynchronous computation.
The std::future::Future
trait definition is:
use std::pin::Pin;
use std::task::{Context, Poll};
pub trait Future {
type Output;
fn poll(self: Pin<&mut Self>, cx: &mut Context)
-> Poll<Self::Output>;
}
The associated type Output
is the type that the future produces once it completes. The Pin
type is how Rust is able to support borrows in async
functions. See the standard library documentation for more details.
Unlike how futures are implemented in other languages, a Rust future does not represent a computation happening in the background, rather the Rust future is the computation itself. The owner of the future is responsible for advancing the computation by polling the future. This is done by calling Future::poll
.
Implementing Future
Let’s implement a very simple future. This future will:
- Wait until a specific instant in time.
- Output some text to STDOUT.
- Yield a string.
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
use std::time::{Duration, Instant};
struct Delay {
when: Instant,
}
impl Future for Delay {
type Output = &'static str;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>)
-> Poll<&'static str>
{
if Instant::now() >= self.when {
println!("Hello world");
Poll::Ready("done")
} else {
// Ignore this line for now.
cx.waker().wake_by_ref();
Poll::Pending
}
}
}
#[tokio::main]
async fn main() {
let when = Instant::now() + Duration::from_millis(10);
let future = Delay { when };
let out = future.await;
assert_eq!(out, "done");
}
Async fn as a Future
In the main function, we instantiate the future and call .await
on it. From async functions, we may call .await
on any value that implements Future
. In turn, calling an async
function returns an anonymous type that implements Future
. In the case of async fn main()
, the generated future is roughly:
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
use std::time::{Duration, Instant};
enum MainFuture {
// Initialized, never polled
State0,
// Waiting on `Delay`, i.e. the `future.await` line.
State1(Delay),
// The future has completed.
Terminated,
}
impl Future for MainFuture {
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>)
-> Poll<()>
{
use MainFuture::*;
loop {
match *self {
State0 => {
let when = Instant::now() +
Duration::from_millis(10);
let future = Delay { when };
*self = State1(future);
}
State1(ref mut my_future) => {
match Pin::new(my_future).poll(cx) {
Poll::Ready(out) => {
assert_eq!(out, "done");
*self = Terminated;
return Poll::Ready(());
}
Poll::Pending => {
return Poll::Pending;
}
}
}
Terminated => {
panic!("future polled after completion")
}
}
}
}
}
Rust futures are state machines. Here, MainFuture
is represented as an enum
of the future’s possible states. The future starts in the State0
state. When poll
is invoked, the future attempts to advance its internal state as much as possible. If the future is able to complete, Poll::Ready
is returned containing the output of the asynchronous computation.
If the future is not able to complete, usually due to resources it is waiting on not being ready, then Poll::Pending
is returned. Receiving Poll::Pending
indicates to the caller that the future will complete at a later time and the caller should invoke poll
again later.
We also see that futures are composed of other futures. Calling poll
on the outer future results in calling the inner future’s poll
function.