Select

Selectという操作では、futureの集合のうち、いずれか1つの準備が整うまで待機し、そのfutureが提供する結果に対して応答します。これはJavaScriptにおけるPromise.raceに似ています。また、Pythonにおける asyncio.wait(task_set, return_when=asyncio.FIRST_COMPLETED)と比べることができます。

Similar to a match statement, the body of select! has a number of arms, each of the form pattern = future => statement. When a future is ready, its return value is destructured by the pattern. The statement is then run with the resulting variables. The statement result becomes the result of the select! macro.

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

#[tokio::main]
async fn main() {
    let (tx, mut rx) = mpsc::channel(32);
    let listener = tokio::spawn(async move {
        tokio::select! {
            Some(msg) = rx.recv() => println!("got: {msg}"),
            _ = sleep(Duration::from_millis(50)) => println!("timeout"),
        };
    });
    sleep(Duration::from_millis(10)).await;
    tx.send(String::from("Hello!")).await.expect("Failed to send greeting");

    listener.await.expect("Listener failed");
}
This slide should take about 5 minutes.
  • The listener async block here is a common form: wait for some async event, or for a timeout. Change the sleep to sleep longer to see it fail. Why does the send also fail in this situation?

  • select! is also often used in a loop in "actor" architectures, where a task reacts to events in a loop. That has some pitfalls, which will be discussed in the next segment.