Crate selectme

source ·
Expand description

github crates.io docs.rs

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));

Macros

  • The inline! macro provides an inlined variant of the select! macro.
  • Waits on multiple concurrent branches, returning when the first branch completes, cancelling the remaining branches.

Structs

  • A biased selector which applies the given random pattern to selection.
  • This is the type produced by the inline! macro unless the static; option is enabled.
  • The implementation used by the select! macro internally and returned by the inline! macro when the static; option is enabled.
  • An unbiased selector which starts from the top and works its way to the bottom.

Attribute Macros

  • Marks async function to be executed by the selected runtime. This macro helps set up a Runtime without requiring the user to use Runtime or Builder directly.
  • Marks async function to be executed by runtime, suitable to test environment