What Are Futures in Rust?

In Rust, a Future is like a placeholder for a value that will be available later. It represents a task that is still running or waiting, for example, downloading data, reading a file, or making an API call, and will eventually complete with a result.

In Rust, futures are a core concept of asynchronous programming. The key point is that Futures allow your program to keep running without waiting for that task to finish.

It’s like ordering food at a restaurant, you place your order (start a future), and when the food is ready, the server delivers it (the future resolves).

Futures are the foundation of Rust’s asynchronous programming model. They make it possible to write non-blocking code that runs efficiently without holding up other tasks.

How Futures Work in Rust

A Future implements the Future trait from the std::future module, for example:

pub trait Future {
type Output; // The type of value this future will produce

fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output>;
}
  • Output: Defines the type of value the future will eventually give.
  • poll(): Checks if the future is ready. If it isn’t, Rust’s executor will try again later.

How To Create a Future In Rust?

In Rust, a Future represents a value that will be available later. You can easily create one using the async keyword.

Example: A Simple Future

async fn my_future() -> u32 {
42
}

#[tokio::main]
async fn main() {
let result = my_future().await;
println!("The future resolved with: {}", result);
}

Chaining Futures with .then()

The .then() method lets you take the result of one future and pass it to the next asynchronous operation.

Example: Chaining Futures

use futures::future;

#[tokio::main]
async fn main() {
let future = future::ready(10); // Creates a future that resolves immediately with 10

let result = future.then(|value| async move {
value * 2 // Multiply the result by 2 in the next future
}).await;

println!("Result: {}", result);
}
  • future::ready(10): Creates a future that is immediately ready with the value 10.
  • .then(|value| async move { … }): Takes the value from the first future (10) and runs another asynchronous block.

How To Combine Multiple Futures?

In Rust, you can run multiple asynchronous tasks at the same time using methods like tokio::join!.

Example: Running Futures Concurrently

use tokio::time::{sleep, Duration};

async fn task_one() {
sleep(Duration::from_secs(1)).await;
println!("Task one completed");
}

async fn task_two() {
sleep(Duration::from_secs(2)).await;
println!("Task two completed");
}

#[tokio::main]
async fn main() {
// Run both futures at the same time
tokio::join!(task_one(), task_two());

println!("Both tasks completed");
}

Output:

Task one completed  
Task two completed
Both tasks completed

Explanation:

  • task_one() and task_two(): Two async functions that simulate work by sleeping for 1 and 2 seconds, respectively.

How To Use Custom Futures?

You can also define custom futures by implementing the Future trait. Your custom future can keep track of its internal state to determine if it’s ready to produce a value or if it needs more time (Poll::Pending vs Poll::Ready).

Example: A Simple Custom Future

use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};

struct MyFuture {
step: u8,
}

impl Future for MyFuture {
type Output = u32;

fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
if self.step == 0 {
self.step += 1;
Poll::Pending // Future is not ready yet
} else {
Poll::Ready(42) // Future has completed with a value
}
}
}

#[tokio::main]
async fn main() {
let my_future = MyFuture { step: 0 };
let result = my_future.await;
println!("Custom future resolved with: {}", result);
}

Error Handling in Futures

You can handle errors in futures using the Result type and .await.

Example: Future with Error Handling

async fn fetch_data() -> Result<String, &'static str> {
Ok("Data fetched successfully".to_string())
}

#[tokio::main]
async fn main() {
match fetch_data().await {
Ok(data) => println!("{}", data),
Err(e) => println!("Error: {}", e),
}
}

Learn Other Topics About Rust

Leave a Comment