Course contentsShow
← Rust
Lesson 1243 of 1,91011. Asynchronous ProgrammingFree lesson

Compositional Patterns in Asynchronous Code

Combine multiple asynchronous operations using join and select macros for concurrent execution flow.

In this lesson, you will learn how to orchestrate multiple concurrent operations using the join! and select! macros to control the execution flow of your asynchronous tasks.

When building complex systems, you often need to run several tasks at once. Rust provides two primary compositional patterns to manage these relationships: aggregation and racing.

Aggregation with join!

Use the join! macro when you have several independent asynchronous tasks that must all finish before your program can proceed. Unlike simply awaiting them one after another (which is sequential), join! polls them concurrently on the same task.

use futures::join;

async fn run_tasks() {
    let task_one = fetch_data_from_db();
    let task_two = ping_external_service();

    // Both tasks run concurrently; execution halts here until both are done.
    let (res_one, res_two) = join!(task_one, task_two);
}

Racing with select!

Sometimes, you don't need every task to finish; you only need the first one to complete, or you need to handle events as they arrive. The select! macro allows you to wait on multiple futures simultaneously and execute code as soon as the first one yields a result. This is essential for patterns like timeouts or handling cancellation signals.

use futures::{future::FutureExt, select};

async fn handle_request(mut request_stream: RequestStream) {
    loop {
        select! {
            req = request_stream.next().fuse() => process(req),
            _ = timeout_future.fuse() => break, // Exit if the timer finishes first
        }
    }
}

Choosing the Right Tool

  • join! is your go-to for "synchronization"—it ensures total completion of a set of dependencies. It is predictable and keeps your logic clean when you need all results combined.
  • select! is your go-to for "reactivity"—it is designed for scenarios where you need to respond to the fastest event, handle timeouts, or gracefully stop background work when a higher-priority event occurs.

By mastering these two macros, you shift from thinking about asynchronous code as a series of steps to thinking about it as a system of concurrent streams.

Key Takeaway: Use join! to synchronize multiple dependent tasks concurrently, and use select! to race multiple operations and respond to whichever completes first.