Syntax

The select! macro can handle more than two branches. The current limit is 64 branches. Each branch is structured as:

  1. <pattern> = <async expression> => <handler>,

When the select macro is evaluated, all the <async expression>s are aggregated and executed concurrently. When an expression completes, the result is matched against <pattern>. If the result matches the pattern, then all remaining async expressions are dropped and <handler> is executed. The <handler> expression has access to any bindings established by <pattern>.

The basic case is <pattern> is a variable name, the result of the async expression is bound to the variable name and <handler> has access to that variable. This is why, in the original example, val was used for <pattern> and <handler> was able to access val.

If <pattern> does not match the result of the async computation, then the remaining async expressions continue to execute concurrently until the next one completes. At this time, the same logic is applied to that result.

Because select! takes any async expression, it is possible to define more complicated computation to select on.

Here, we select on the output of a oneshot channel and a TCP connection.

  1. use tokio::net::TcpStream;
  2. use tokio::sync::oneshot;
  3. #[tokio::main]
  4. async fn main() {
  5. let (tx, rx) = oneshot::channel();
  6. // Spawn a task that sends a message over the oneshot
  7. tokio::spawn(async move {
  8. tx.send("done").unwrap();
  9. });
  10. tokio::select! {
  11. socket = TcpStream::connect("localhost:3465") => {
  12. println!("Socket connected {:?}", socket);
  13. }
  14. msg = rx => {
  15. println!("received message first {:?}", msg);
  16. }
  17. }
  18. }

Here, we select on a oneshot and accepting sockets from a TcpListener.

  1. use tokio::net::TcpListener;
  2. use tokio::sync::oneshot;
  3. use std::io;
  4. #[tokio::main]
  5. async fn main() -> io::Result<()> {
  6. let (tx, rx) = oneshot::channel();
  7. tokio::spawn(async move {
  8. tx.send(()).unwrap();
  9. });
  10. let mut listener = TcpListener::bind("localhost:3465").await?;
  11. tokio::select! {
  12. _ = async {
  13. loop {
  14. let (socket, _) = listener.accept().await?;
  15. tokio::spawn(async move { process(socket) });
  16. }
  17. // Help the rust type inferencer out
  18. Ok::<_, io::Error>(())
  19. } => {}
  20. _ = rx => {
  21. println!("terminating accept loop");
  22. }
  23. }
  24. Ok(())
  25. }

The accept loop runs until an error is encountered or rx receives a value. The _ pattern indicates that we have no interest in the return value of the async computation.