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/.awaitin Rust. - Familiarity with Rust's trait system.
🎯 Article Outline: What You'll Master
In this article, you will learn:
- ✅ The
FutureTrait: 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
Futuretrait.
🧠 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: Thepollmethod is called by the async runtime to check if the future is ready.Poll<T>: Thepollmethod returns aPoll<T>, which can be eitherPoll::Ready(T)orPoll::Pending.
💻 Section 2: Deep Dive - How Futures are Polled
When you .await a future, the async runtime calls its poll method.
- If
pollreturnsPoll::Ready(value), then.awaitreturns thevalue. - If
pollreturnsPoll::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:
- Define a struct to hold the state of our future.
- Implement the
Futuretrait for the struct. - Use our custom future in an
asyncfunction.
✨ Section 6: Best Practices and Anti-Patterns in Rust
Rust Best Practices (Idiomatic Way):
- Do this: Use
async/.awaitinstead of manually implementingFuturewhenever 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
Futuretrait represents a value that may not be ready yet. - Async runtimes poll futures to drive them to completion.
- You can implement the
Futuretrait 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.