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::{self, Receiver};
use tokio::time::{sleep, Duration};

#[derive(Debug, PartialEq)]
enum Animal {
    Cat { name: String },
    Dog { name: String },
}

async fn first_animal_to_finish_race(
    mut cat_rcv: Receiver<String>,
    mut dog_rcv: Receiver<String>,
) -> Option<Animal> {
    tokio::select! {
        cat_name = cat_rcv.recv() => Some(Animal::Cat { name: cat_name? }),
        dog_name = dog_rcv.recv() => Some(Animal::Dog { name: dog_name? })
    }
}

#[tokio::main]
async fn main() {
    let (cat_sender, cat_receiver) = mpsc::channel(32);
    let (dog_sender, dog_receiver) = mpsc::channel(32);
    tokio::spawn(async move {
        sleep(Duration::from_millis(500)).await;
        cat_sender.send(String::from("Felix")).await.expect("Failed to send cat.");
    });
    tokio::spawn(async move {
        sleep(Duration::from_millis(50)).await;
        dog_sender.send(String::from("Rex")).await.expect("Failed to send dog.");
    });

    let winner = first_animal_to_finish_race(cat_receiver, dog_receiver)
        .await
        .expect("Failed to receive winner");

    println!("Winner is {winner:?}");
}
  • In this example, we have a race between a cat and a dog. first_animal_to_finish_race listens to both channels and will pick whichever arrives first. Since the dog takes 50ms, it wins against the cat that take 500ms.

  • この例ではoneshotチャネルを使うこともできます。なぜなら、チャネルは一回きりのsendを受け取ることになっているからです。

  • レースに制限時間を追加することによって、違う種類のfutureをselectすることを実演してみてください。

  • select!はマッチしなかったブランチをドロップすることに注意してください。これは、そうしたブランチのfutureがキャンセルされることにつながります。select!を毎回実行する際に新たなfutureが作成されるときに、select!を使うのが最も簡単です。

    • Futureそのものでなく、&mut futureを渡すという代替案もあります。しかし、これは問題につながることもあります。このことはPinに関するスライドで詳細に議論します。