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.

  1. use tokio::net::TcpStream;
  2. async fn my_async_fn() {
  3. println!("hello from async");
  4. let _socket = TcpStream::connect("127.0.0.1:3000").await.unwrap();
  5. println!("async TCP operation complete");
  6. }

We call the function and it returns some value. We call .await on that value.

  1. #[tokio::main]
  2. async fn main() {
  3. let what_is_this = my_async_fn();
  4. // Nothing has been printed yet.
  5. what_is_this.await;
  6. // Text has been printed and socket has been
  7. // established and closed.
  8. }

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:

  1. use std::pin::Pin;
  2. use std::task::{Context, Poll};
  3. pub trait Future {
  4. type Output;
  5. fn poll(self: Pin<&mut Self>, cx: &mut Context)
  6. -> Poll<Self::Output>;
  7. }

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:

  1. Wait until a specific instant in time.
  2. Output some text to STDOUT.
  3. Yield a string.
  1. use std::future::Future;
  2. use std::pin::Pin;
  3. use std::task::{Context, Poll};
  4. use std::time::{Duration, Instant};
  5. struct Delay {
  6. when: Instant,
  7. }
  8. impl Future for Delay {
  9. type Output = &'static str;
  10. fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>)
  11. -> Poll<&'static str>
  12. {
  13. if Instant::now() >= self.when {
  14. println!("Hello world");
  15. Poll::Ready("done")
  16. } else {
  17. // Ignore this line for now.
  18. cx.waker().wake_by_ref();
  19. Poll::Pending
  20. }
  21. }
  22. }
  23. #[tokio::main]
  24. async fn main() {
  25. let when = Instant::now() + Duration::from_millis(10);
  26. let future = Delay { when };
  27. let out = future.await;
  28. assert_eq!(out, "done");
  29. }

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:

  1. use std::future::Future;
  2. use std::pin::Pin;
  3. use std::task::{Context, Poll};
  4. use std::time::{Duration, Instant};
  5. enum MainFuture {
  6. // Initialized, never polled
  7. State0,
  8. // Waiting on `Delay`, i.e. the `future.await` line.
  9. State1(Delay),
  10. // The future has completed.
  11. Terminated,
  12. }
  13. impl Future for MainFuture {
  14. type Output = ();
  15. fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>)
  16. -> Poll<()>
  17. {
  18. use MainFuture::*;
  19. loop {
  20. match *self {
  21. State0 => {
  22. let when = Instant::now() +
  23. Duration::from_millis(10);
  24. let future = Delay { when };
  25. *self = State1(future);
  26. }
  27. State1(ref mut my_future) => {
  28. match Pin::new(my_future).poll(cx) {
  29. Poll::Ready(out) => {
  30. assert_eq!(out, "done");
  31. *self = Terminated;
  32. return Poll::Ready(());
  33. }
  34. Poll::Pending => {
  35. return Poll::Pending;
  36. }
  37. }
  38. }
  39. Terminated => {
  40. panic!("future polled after completion")
  41. }
  42. }
  43. }
  44. }
  45. }

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.