In Rust, a 'Future' represents an asynchronous computation that may complete at some point in the future. It's a fundamental building block for asynchronous programming, enabling non-blocking operations and efficient resource utilization, especially for I/O-bound tasks.
What is a Future?
A `Future` is a trait (`std::future::Future`) that defines a single method: `poll`. This method is called repeatedly by an 'executor' (an asynchronous runtime like `tokio` or `async-std`) to check if the computation has made progress or completed.
How Futures Work:
1. `poll` Method: When an executor needs to advance an asynchronous task, it calls the `poll` method of the task's future.
2. `Poll::Pending`: If the computation is not yet complete (e.g., waiting for network data, a file read, or a timer), `poll` returns `Poll::Pending`. When returning `Pending`, the future typically registers a 'Waker' with the current `Context`. The `Waker` is a mechanism for the future (or the underlying I/O driver it's interacting with) to signal the executor that it's ready to make further progress, prompting the executor to `poll` it again.
3. `Poll::Ready(value)`: If the computation has completed and produced a result, `poll` returns `Poll::Ready` containing the output value.
4. Executor's Role: The executor's job is to manage a set of futures, poll them, and react to their `Poll::Pending` or `Poll::Ready` states, efficiently scheduling them to run without blocking the main thread.
`async` and `await`:
Rust's `async` keyword allows you to write asynchronous code that *looks* synchronous. An `async fn` or `async` block transforms your code into a state machine that implements the `Future` trait. The `await` keyword is then used to pause the execution of the current `async` block until another `Future` resolves (`Poll::Ready`). When an `await` encounters a `Poll::Pending` future, it yields control back to the executor, allowing other tasks to run.
Why use Futures?
* Concurrency without Threads: Achieve high concurrency without the overhead of many OS threads, which can be expensive in terms of memory and context switching.
* Non-blocking I/O: Perform I/O operations (network, disk) asynchronously, meaning the program doesn't halt while waiting for these operations to complete.
* Efficiency: Better utilization of CPU resources by switching between tasks that are ready to run, rather than blocking on I/O.
Ecosystem:
While `std::future::Future` is in the standard library, you typically need an asynchronous runtime (executor) to actually *run* futures. Popular runtimes include `tokio` and `async-std`.
Example Code
```rust
// To run this example, add the following to your Cargo.toml:
// [dependencies]
// tokio = { version = "1", features = ["full"] }
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
use std::time::Duration;
/// A simple custom Future that counts down a number of polls until it's ready.
/// This demonstrates the basic `poll` mechanism with `Pending` and `Ready` states.
struct CountDownFuture {
countdown: u8,
initial_countdown: u8,
}
impl CountDownFuture {
fn new(start: u8) -> Self {
CountDownFuture { countdown: start, initial_countdown: start }
}
}
impl Future for CountDownFuture {
// The type of value that the future will resolve to once ready.
type Output = String;
// The core method that an executor will call repeatedly to make progress.
fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
// For simplicity, this example doesn't explicitly use a Waker here.
// In a real-world scenario (e.g., waiting for I/O), the future would
// register the provided `cx.waker()` to be notified when the underlying
// event (like data arrival) occurs, prompting the executor to re-poll.
// Decrement the counter for each poll.
self.countdown -= 1;
if self.countdown == 0 {
// When countdown reaches 0, the future is ready.
println!("[CountDownFuture] Ready after {} polls!", self.initial_countdown);
Poll::Ready("Countdown completed!".to_string())
} else {
// Otherwise, it's still pending.
println!("[CountDownFuture] Pending, {} polls left.", self.countdown);
// The executor will typically re-poll futures that return Pending
// until they become Ready, especially when they are part of an
// `async` block being `await`ed.
Poll::Pending
}
}
}
#[tokio::main] // This macro sets up the tokio runtime (executor) for `async fn main`.
async fn main() {
println!("Starting main async block.\n");
// --- 1. Demonstrate a custom Future ---
println!("--- Custom CountDownFuture Example ---");
let countdown_future = CountDownFuture::new(3); // This future will take 3 polls to complete
let result_custom = countdown_future.await;
println!("Result from custom future: {}\n", result_custom);
// --- 2. Demonstrate a standard library async operation (which returns a Future) ---
println!("--- tokio::time::sleep Example ---");
let start_sleep = std::time::Instant::now();
println!("Waiting for 500ms using tokio::time::sleep...");
// `tokio::time::sleep` returns a Future that resolves after the specified duration.
tokio::time::sleep(Duration::from_millis(500)).await;
println!("tokio::time::sleep finished after {:?}.\n", start_sleep.elapsed());
// --- 3. Demonstrate combining multiple Futures concurrently with `tokio::join!` ---
println!("--- Combining Futures with tokio::join! ---");
let fut1 = async {
tokio::time::sleep(Duration::from_secs(1)).await;
"Future 1 done"
};
let fut2 = async {
tokio::time::sleep(Duration::from_millis(700)).await;
"Future 2 done"
};
// `tokio::join!` runs multiple futures concurrently and waits for all of them to complete.
// It's a convenient way to await multiple futures without blocking.
let (res1, res2) = tokio::join!(fut1, fut2);
println!("Results from joined futures: '{}' and '{}'\n", res1, res2);
println!("Main async block finished.");
}
```








Futures in Rust