Search code examples
rustrust-tokio

What is the proper way to do this in Rust?


Suppose a loop on main like this:

use crate::modules::very_ext::{one, two};
use crate::modules::much_wow::logic;

let result;
loop {
  let foo = one.next().await.expect("boom");

  // Do something with foo
  result = logic(foo)
}

This far, great. What happens when the logic inside the loop needs to "react" to multiple external events?

If we do something like this:

use crate::modules::very_ext::{one, two};
use crate::modules::much_wow::logic;

let result;
loop {
  let foo = one.next().await.expect("boom");
  let bar = two.next().await.expect("boom");

  // Do something with foo
  result = logic(foo, bar)
}

then logic will only run after:

  • foo resolves first
  • then bar eventually resolves as well

(which is not what we want, we need to react whenever a new value for either foo or bar is externally ready to be passed to the main loop).

By reading tokio's docs, my guess is that this can be solved by using threads and channels. But I'm confused on exactly how. I am thinking it like this:

  • Create an mpsc channel.
  • Spawn one and two into their own threads.
  • Each thread pushes a value on the channel with perhaps some encapsulation that distinguishes it.
  • The main loop is the single consumer that "reacts" as the values come in over the mpsc channel.

Am I overthinking this? Is there a simpler way (assume the general problem with multiple producers, not just two)?

Thank you!


Solution

  • You can spawn lightweight tasks out of both futures, which will allow you to await them both without serializing them. It is conceptually similar to the solution you proposed, but doesn't use additional threads (it runs foo and bar on the normal executor threads, interleaving them with other futures as they await), and the channels created are one-shot channels highly optimized for this purpose.

    loop {
        // spawn foo and bar to run in the background
        let foo = tokio::spawn(one.next());
        let bar = tokio::spawn(two.next());
        // awaiting foo now also executes bar
        let foo = foo.await.unwrap().expect("boom");
        let bar = bar.await.unwrap().expect("boom");
    
        result = logic(foo, bar);
    }