Repetitive arms in `select!` macro

Hi everyone,

let's assume I have code similar to this Playground. But instead of only having t1 and t2, I have at least 8 more.

Is there a possible solution to not repeat myself over and over again? The problem is, that I need an else branch as well as at least two other, different branches (see the other_function call).

Playground code
#![allow(unused_variables)]

use async_trait::async_trait;
use serde::{de::DeserializeOwned, Serialize};
use std::borrow::Cow;

enum CustomError {
    NotSupported,
}

#[async_trait]
trait Process {
    type Output: Serialize;
    type Input: DeserializeOwned + Send;

    fn name(&self) -> Cow<str>;

    async fn get_data(&mut self) -> Result<Self::Output, CustomError> {
        Err(CustomError::NotSupported)
    }
    async fn set_data(&mut self, _data: Self::Input) -> Result<(), CustomError> {
        Err(CustomError::NotSupported)
    }
}

struct T1;
struct T2;

#[async_trait]
impl Process for T1 {
    type Output = u32;
    type Input = String;

    fn name(&self) -> Cow<str> {
        "T1".into()
    }

    async fn get_data(&mut self) -> Result<Self::Output, CustomError> {
        Ok(3)
    }

    async fn set_data(&mut self, data: Self::Input) -> Result<(), CustomError> {
        println!("{}", data);
        Ok(())
    }
}

#[async_trait]
impl Process for T2 {
    type Output = String;
    type Input = ();

    fn name(&self) -> Cow<str> {
        "T2".into()
    }

    async fn get_data(&mut self) -> Result<Self::Output, CustomError> {
        Ok(String::from("Hi!"))
    }
}

async fn send<T>(val: T) {
    todo!()
}

async fn other_function() -> Result<(), ()> {
    todo!()
}

#[tokio::main]
async fn main() {
    let mut t1 = T1;
    let mut t2 = T2;

    loop {
        tokio::select! {
            other_val = other_function() => {
                todo!()
            }

            Ok(val) = t1.get_data() => {
                send(val).await
            }
            Ok(val) = t2.get_data() => {
                send(val).await
            }
            else => {
                eprintln!("What happened‽");
                break;
            }
        }
    }
}

I suspect someone will be along shortly with a tokio-specific solution, but you can always write your own macro inside main as a last resort:

macro_rules! event_loop_body {
    ($($process:ident),*) => {
        tokio::select! {
            other_val = other_function() => {
                todo!()
            }

            $(
                Ok(val) = $process.get_data() => {
                    send(val).await
                }
            )*
           
            else => {
                eprintln!("What happened‽");
                break;
            }
        }
    }
}

loop { event_loop_body! ( t1, t2 ); }
1 Like

Hmm, true. Looks like a decent solution. I always thought: How do I get the else branch and the rest inside the event_loop_body! macro. But yeah.. why not type them in the macro itself.

:slight_smile:

Let's see if somebody has another good solution.

You could also go for this:

loop {
    let val = tokio::select! {
        other_val = other_function() => {
            todo!()
            continue;
        }
        Ok(val) = t1.get_data() => val,
        Ok(val) = t2.get_data() => val,
        else => {
            eprintln!("What happened‽");
            break;
        }
    };
    send(val).await;
}

Sadly not, because t1.get_data will return Result<u32, _> and t2.get_data Result<String, _> (for example). So you have multiple distinct types in the same match arm.

This is another reasonable option, since the repeated code is short:

#[tokio::main]
async fn main() {
    let mut t1 = T1;
    let mut t2 = T2;

    loop {
        #[rustfmt::skip]
        tokio::select! {
            other_val = other_function() => {
                todo!()
            }

            Ok(val) = t1.get_data() => { send(val).await }
            Ok(val) = t2.get_data() => { send(val).await }

            else => {
                eprintln!("What happened‽");
                break;
            }
        }
    }
}

Edit: the select! docs say that they take arbitrary async expressions, so could something like this be made to work? It’s not exactly equivalent because the send could get cancelled after data is read:

macro_rules! forward_data {
    ($($process:ident),*) => {
        async {
            tokio::select! {
                $(
                    Ok(val) = $process.get_data() => {
                        send(val).await
                    }
                )*
            }
        }
    }
}

loop {
    tokio::select! {
        other_val = other_function() => {
            todo!()
        }

        _ = forward_data!(t1,t2) => ()
           
        else => {
            eprintln!("What happened‽");
            break;
        }
    }
}

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.