#![forbid(unsafe_code)]
#![allow(clippy::many_single_char_names)]
mod option_ab;
mod option_abc;
mod option_abcd;
mod option_abcde;
pub use option_ab::*;
pub use option_abc::*;
pub use option_abcd::*;
pub use option_abcde::*;
use core::future::Future;
use core::pin::Pin;
use core::task::{Context, Poll};
struct PendingFuture;
impl Unpin for PendingFuture {}
impl Future for PendingFuture {
type Output = ();
fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
Poll::Pending
}
}
#[must_use = "futures stay idle unless you await them"]
pub struct SelectFuture<A, B, C, D, E, FutA, FutB, FutC, FutD, FutE>
where
FutA: Future<Output = A> + Send + Unpin + 'static,
FutB: Future<Output = B> + Send + Unpin + 'static,
FutC: Future<Output = C> + Send + Unpin + 'static,
FutD: Future<Output = D> + Send + Unpin + 'static,
FutE: Future<Output = E> + Send + Unpin + 'static,
{
a: FutA,
b: FutB,
c: FutC,
d: FutD,
e: FutE,
}
impl<A, B, C, D, E, FutA, FutB, FutC, FutD, FutE>
SelectFuture<A, B, C, D, E, FutA, FutB, FutC, FutD, FutE>
where
FutA: Future<Output = A> + Send + Unpin + 'static,
FutB: Future<Output = B> + Send + Unpin + 'static,
FutC: Future<Output = C> + Send + Unpin + 'static,
FutD: Future<Output = D> + Send + Unpin + 'static,
FutE: Future<Output = E> + Send + Unpin + 'static,
{
pub fn new(
a: FutA,
b: FutB,
c: FutC,
d: FutD,
e: FutE,
) -> SelectFuture<A, B, C, D, E, FutA, FutB, FutC, FutD, FutE> {
SelectFuture { a, b, c, d, e }
}
}
impl<A, B, C, D, E, FutA, FutB, FutC, FutD, FutE> Future
for SelectFuture<A, B, C, D, E, FutA, FutB, FutC, FutD, FutE>
where
FutA: Future<Output = A> + Send + Unpin + 'static,
FutB: Future<Output = B> + Send + Unpin + 'static,
FutC: Future<Output = C> + Send + Unpin + 'static,
FutD: Future<Output = D> + Send + Unpin + 'static,
FutE: Future<Output = E> + Send + Unpin + 'static,
{
type Output = OptionAbcde<A, B, C, D, E>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut_self = self.get_mut();
match Pin::new(&mut mut_self.a).poll(cx) {
Poll::Ready(value) => return Poll::Ready(OptionAbcde::A(value)),
Poll::Pending => {}
}
match Pin::new(&mut mut_self.b).poll(cx) {
Poll::Ready(value) => return Poll::Ready(OptionAbcde::B(value)),
Poll::Pending => {}
}
match Pin::new(&mut mut_self.c).poll(cx) {
Poll::Ready(value) => return Poll::Ready(OptionAbcde::C(value)),
Poll::Pending => {}
}
match Pin::new(&mut mut_self.d).poll(cx) {
Poll::Ready(value) => return Poll::Ready(OptionAbcde::D(value)),
Poll::Pending => {}
}
match Pin::new(&mut mut_self.e).poll(cx) {
Poll::Ready(value) => return Poll::Ready(OptionAbcde::E(value)),
Poll::Pending => {}
}
Poll::Pending
}
}
#[allow(clippy::missing_panics_doc)]
pub async fn select_ab<A, B, FutA, FutB>(a: FutA, b: FutB) -> OptionAb<A, B>
where
FutA: Future<Output = A> + Send + 'static,
FutB: Future<Output = B> + Send + 'static,
{
match SelectFuture::new(
Box::pin(a),
Box::pin(b),
PendingFuture {},
PendingFuture {},
PendingFuture {},
)
.await
{
OptionAbcde::A(value) => OptionAb::A(value),
OptionAbcde::B(value) => OptionAb::B(value),
_ => unreachable!(),
}
}
#[allow(clippy::missing_panics_doc)]
pub async fn select_abc<A, B, C, FutA, FutB, FutC>(a: FutA, b: FutB, c: FutC) -> OptionAbc<A, B, C>
where
FutA: Future<Output = A> + Send + 'static,
FutB: Future<Output = B> + Send + 'static,
FutC: Future<Output = C> + Send + 'static,
{
match SelectFuture::new(
Box::pin(a),
Box::pin(b),
Box::pin(c),
PendingFuture {},
PendingFuture {},
)
.await
{
OptionAbcde::A(value) => OptionAbc::A(value),
OptionAbcde::B(value) => OptionAbc::B(value),
OptionAbcde::C(value) => OptionAbc::C(value),
_ => unreachable!(),
}
}
#[allow(clippy::missing_panics_doc)]
pub async fn select_abcd<A, B, C, D, FutA, FutB, FutC, FutD>(
a: FutA,
b: FutB,
c: FutC,
d: FutD,
) -> OptionAbcd<A, B, C, D>
where
FutA: Future<Output = A> + Send + 'static,
FutB: Future<Output = B> + Send + 'static,
FutC: Future<Output = C> + Send + 'static,
FutD: Future<Output = D> + Send + 'static,
{
match SelectFuture::new(
Box::pin(a),
Box::pin(b),
Box::pin(c),
Box::pin(d),
PendingFuture {},
)
.await
{
OptionAbcde::A(value) => OptionAbcd::A(value),
OptionAbcde::B(value) => OptionAbcd::B(value),
OptionAbcde::C(value) => OptionAbcd::C(value),
OptionAbcde::D(value) => OptionAbcd::D(value),
OptionAbcde::E(_) => unreachable!(),
}
}
pub async fn select_abcde<A, B, C, D, E, FutA, FutB, FutC, FutD, FutE>(
a: FutA,
b: FutB,
c: FutC,
d: FutD,
e: FutE,
) -> OptionAbcde<A, B, C, D, E>
where
FutA: Future<Output = A> + Send + 'static,
FutB: Future<Output = B> + Send + 'static,
FutC: Future<Output = C> + Send + 'static,
FutD: Future<Output = D> + Send + 'static,
FutE: Future<Output = E> + Send + 'static,
{
SelectFuture::new(
Box::pin(a),
Box::pin(b),
Box::pin(c),
Box::pin(d),
Box::pin(e),
)
.await
}
#[cfg(test)]
#[allow(clippy::float_cmp)]
mod tests {
use super::*;
use core::ops::Range;
use core::time::Duration;
use safina_async_test::async_test;
use safina_timer::sleep_for;
use std::time::Instant;
pub fn expect_elapsed(before: Instant, range_ms: Range<u64>) {
if range_ms.is_empty() {
panic!("invalid range {:?}", range_ms)
}
let elapsed = before.elapsed();
let duration_range =
Duration::from_millis(range_ms.start)..Duration::from_millis(range_ms.end);
if !duration_range.contains(&elapsed) {
panic!("{:?} elapsed, out of range {:?}", elapsed, duration_range);
}
}
#[async_test]
async fn should_return_proper_types() {
let _: OptionAb<u8, bool> = select_ab(async { 42_u8 }, async { true }).await;
let _: OptionAbc<u8, bool, &'static str> =
select_abc(async { 42_u8 }, async { true }, async { "s1" }).await;
let _: OptionAbcd<u8, bool, &'static str, usize> =
select_abcd(async { 42_u8 }, async { true }, async { "s1" }, async {
7_usize
})
.await;
let _: OptionAbcde<u8, bool, &'static str, usize, f32> = select_abcde(
async { 42_u8 },
async { true },
async { "s1" },
async { 7_usize },
async { 0.99_f32 },
)
.await;
}
#[async_test]
async fn all_complete() {
select_ab(async { 42_u8 }, async { true }).await.unwrap_a();
select_abc(async { 42_u8 }, async { true }, async { "s1" })
.await
.unwrap_a();
select_abcd(async { 42_u8 }, async { true }, async { "s1" }, async {
7_usize
})
.await
.unwrap_a();
select_abcde(
async { 42_u8 },
async { true },
async { "s1" },
async { 7_usize },
async { 0.99_f32 },
)
.await
.unwrap_a();
}
#[async_test]
async fn one_complete() {
let ready_a = || async { 42_u8 };
let ready_b = || async { true };
let ready_c = || async { "s1" };
let ready_d = || async { 7_usize };
let ready_e = || async { 0.99_f32 };
let wait_a = || async {
sleep_for(Duration::from_millis(10)).await;
42_u8
};
let wait_b = || async {
sleep_for(Duration::from_millis(10)).await;
true
};
let wait_c = || async {
sleep_for(Duration::from_millis(10)).await;
"s1"
};
let wait_d = || async {
sleep_for(Duration::from_millis(10)).await;
7_usize
};
let wait_e = || async {
sleep_for(Duration::from_millis(10)).await;
0.99_f32
};
assert_eq!(42_u8, select_ab(ready_a(), wait_b()).await.unwrap_a());
assert_eq!(true, select_ab(wait_a(), ready_b()).await.unwrap_b());
assert_eq!(
42_u8,
select_abc(ready_a(), wait_b(), wait_c()).await.unwrap_a()
);
assert_eq!(
true,
select_abc(wait_a(), ready_b(), wait_c()).await.unwrap_b()
);
assert_eq!(
"s1",
select_abc(wait_a(), wait_b(), ready_c()).await.unwrap_c()
);
assert_eq!(
42_u8,
select_abcd(ready_a(), wait_b(), wait_c(), wait_d())
.await
.unwrap_a()
);
assert_eq!(
true,
select_abcd(wait_a(), ready_b(), wait_c(), wait_d())
.await
.unwrap_b()
);
assert_eq!(
"s1",
select_abcd(wait_a(), wait_b(), ready_c(), wait_d())
.await
.unwrap_c()
);
assert_eq!(
7_usize,
select_abcd(wait_a(), wait_b(), wait_c(), ready_d())
.await
.unwrap_d()
);
assert_eq!(
42_u8,
select_abcde(ready_a(), wait_b(), wait_c(), wait_d(), wait_e())
.await
.unwrap_a()
);
assert_eq!(
true,
select_abcde(wait_a(), ready_b(), wait_c(), wait_d(), wait_e())
.await
.unwrap_b()
);
assert_eq!(
"s1",
select_abcde(wait_a(), wait_b(), ready_c(), wait_d(), wait_e())
.await
.unwrap_c()
);
assert_eq!(
7_usize,
select_abcde(wait_a(), wait_b(), wait_c(), ready_d(), wait_e())
.await
.unwrap_d()
);
assert_eq!(
0.99_f32,
select_abcde(wait_a(), wait_b(), wait_c(), wait_d(), ready_e())
.await
.unwrap_e()
);
}
#[async_test]
async fn should_poll_all() {
let (sender_a, receiver_a) = std::sync::mpsc::channel::<()>();
let (sender_b, receiver_b) = std::sync::mpsc::channel::<()>();
let (sender_c, receiver_c) = std::sync::mpsc::channel::<()>();
let (sender_d, receiver_d) = std::sync::mpsc::channel::<()>();
let (sender_e, receiver_e) = std::sync::mpsc::channel::<()>();
let fut_a = async move {
sender_a.send(()).unwrap();
sleep_for(Duration::from_millis(10)).await;
};
let fut_b = async move {
sender_b.send(()).unwrap();
sleep_for(Duration::from_millis(10)).await;
};
let fut_c = async move {
sender_c.send(()).unwrap();
sleep_for(Duration::from_millis(10)).await;
};
let fut_d = async move {
sender_d.send(()).unwrap();
sleep_for(Duration::from_millis(10)).await;
};
let fut_e = async move {
sender_e.send(()).unwrap();
sleep_for(Duration::from_millis(10)).await;
};
select_abcde(fut_a, fut_b, fut_c, fut_d, fut_e).await;
receiver_a.recv().unwrap();
receiver_b.recv().unwrap();
receiver_c.recv().unwrap();
receiver_d.recv().unwrap();
receiver_e.recv().unwrap();
}
#[async_test]
async fn awaits_a() {
let before = Instant::now();
select_ab(
async move {
sleep_for(Duration::from_millis(100)).await;
42_u8
},
async move {
sleep_for(Duration::from_millis(200)).await;
true
},
)
.await
.unwrap_a();
expect_elapsed(before, 100..190);
}
#[async_test]
async fn awaits_b() {
let before = Instant::now();
select_ab(
async move {
sleep_for(Duration::from_millis(200)).await;
42_u8
},
async move {
sleep_for(Duration::from_millis(100)).await;
true
},
)
.await
.unwrap_b();
expect_elapsed(before, 100..190);
}
#[async_test]
async fn match_expression() {
match select_ab(async move { 42_u8 }, async move { true }).await {
OptionAb::A(42_u8) => {}
_ => unreachable!(),
}
match select_abcde(
async { 42_u8 },
async { true },
async { "s1" },
async { 7_usize },
async { 0.99_f32 },
)
.await
{
OptionAbcde::A(42_u8) => {}
OptionAbcde::B(true) | OptionAbcde::C("s1") | OptionAbcde::D(7_usize) => unreachable!(),
OptionAbcde::E(value) if value == 0.99_f32 => unreachable!(),
_ => unreachable!(),
}
}
}