#async #tokio #macro #programming #select #branch #fair

no-std selectme

A fast and fair select! macro for asynchronous Rust

17 releases

0.7.2 Mar 22, 2023
0.7.0 Feb 17, 2022

#377 in Asynchronous


Used in selectme-macros

MIT/Apache

56KB
654 lines

selectme

github crates.io docs.rs build status

A fast and fair select! implementation for asynchronous programming.

See the select! or inline! macros for documentation.


Usage

Add the following to your Cargo.toml:

selectme = "0.7.1"

Examples

The following is a simple example showcasing two branches being polled concurrently. For more documentation see select!.

async fn do_stuff_async() {
    // work here
}

async fn more_async_work() {
    // work here
}

selectme::select! {
    _ = do_stuff_async() => {
        println!("do_stuff_async() completed first")
    }
    _ = more_async_work() => {
        println!("more_async_work() completed first")
    }
};

Entrypoint macros

This crate provides entrypoint attributes which are compatible with the ones provided by Tokio through #[selectme::main] and #[selectme::test] with one exception. They do not check (because they cannot) which Tokio features are enabled and simply assumes that you want to build a multithreaded runtime unless flavor is specified.

So why does this project provide entrypoint macros? Well, there's a handful of issues related to performance and ergonomics which turns out to be quite hard to fix in Tokio proper since backwards compatibility needs to be maintained. So until a Tokio 2.x is released and we can bake another breaking release. Until such a time, you can find those macros here.


The inline! macro

The inline! macro provides an inlined variant of the select! macro.

Instead of awaiting directly it evaluates to an instance of the Select or StaticSelect allowing for more efficient multiplexing and complex control flow.

When combined with the static; option it performs the least amount of magic possible to multiplex multiple asynchronous operations making it suitable for efficient and custom abstractions.

use std::time::Duration;
use tokio::time;

async fn async_operation() -> u32 {
    // work here
}

let output = selectme::inline! {
    output = async_operation() => Some(output),
    () = time::sleep(Duration::from_secs(5)) => None,
}.await;

match output {
    Some(output) => {
        assert_eq!(output, 42);
    }
    None => {
        panic!("operation timed out!")
    }
}

The more interesting trick is producing a StaticSelect through the static; option which can be properly named and used inside of another future.

use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
use std::time::Duration;

use pin_project::pin_project;
use selectme::{Random, StaticSelect};
use tokio::time::{self, Sleep};

#[pin_project]
struct MyFuture {
    #[pin]
    select: StaticSelect<u8, (Sleep, Sleep), Random, Option<u32>>,
}

impl Future for MyFuture {
    type Output = Option<u32>;

    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        let this = self.project();
        this.select.poll_next(cx)
    }
}

let s1 = time::sleep(Duration::from_millis(100));
let s2 = time::sleep(Duration::from_millis(200));

let my_future = MyFuture {
    select: selectme::inline! {
        static;

        () = s1 => Some(1),
        _ = s2 => Some(2),
        else => None,
    }
};

assert_eq!(my_future.await, Some(1));

Dependencies