Skip to main content

Working with Futures

In our last article, we were introduced to Async/Await in Rust. Now, let's take a closer look at the Future trait, which is the foundation of async/.await.


📚 Prerequisites

  • A good understanding of async/.await in Rust.
  • Familiarity with Rust's trait system.

🎯 Article Outline: What You'll Master

In this article, you will learn:

  • The Future Trait: What it is and how it works.
  • Polling a Future: How an async runtime polls a future to drive it to completion.
  • Building Your Own Future: How to manually implement the Future trait.

🧠 Section 1: The Core Concepts of the Future Trait

The Future trait has a single method, poll:

trait Future {
type Output;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;
}

Key Principles:

  • Output: The type of value that the future will produce when it's ready.
  • poll: The poll method is called by the async runtime to check if the future is ready.
  • Poll<T>: The poll method returns a Poll<T>, which can be either Poll::Ready(T) or Poll::Pending.

💻 Section 2: Deep Dive - How Futures are Polled

When you .await a future, the async runtime calls its poll method.

  • If poll returns Poll::Ready(value), then .await returns the value.
  • If poll returns Poll::Pending, the runtime will park the current task and wake it up later when it might be able to make progress.

🛠️ Section 3: Project-Based Example: Implementing a Simple Future

Let's implement a simple future that completes after a certain amount of time:

// main.rs
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 = ();

fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
if Instant::now() >= self.when {
Poll::Ready(())
} else {
// Ignore waker for simplicity
Poll::Pending
}
}
}

#[tokio::main]
async fn main() {
let when = Instant::now() + Duration::from_millis(10);
let delay = Delay { when };

delay.await;
println!("done!");
}

The Goal: To create a custom future that completes after a delay.

The Plan:

  1. Define a struct to hold the state of our future.
  2. Implement the Future trait for the struct.
  3. Use our custom future in an async function.

✨ Section 6: Best Practices and Anti-Patterns in Rust

Rust Best Practices (Idiomatic Way):

  • Do this: Use async/.await instead of manually implementing Future whenever possible.

💡 Conclusion & Key Takeaways

You've learned about the Future trait and how it's the foundation of async/.await in Rust.

Let's summarize the key Rust takeaways:

  • The Future trait represents a value that may not be ready yet.
  • Async runtimes poll futures to drive them to completion.
  • You can implement the Future trait manually, but it's often not necessary.

➡️ Next Steps

In the next article, "Project: A Multi-threaded Web Server (Part 1)", we'll put everything we've learned about concurrency together to build a real-world application.


Further Reading (Rust Resources)