A fast and fair select! implementation for asynchronous programming.
See the select! or inline! macros for documentation.
Add the following to your Cargo.toml
:
selectme = "0.7.3"
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")
}
};
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 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));