rtx 0.1.0

RTx is a zero-cost runtime-abstraction intended for use by Rust libraries to enable the Freedom of Choice between asynchronous runtimes.
use std::any::Any;
use std::future::Future;
use std::marker::PhantomData;
use std::pin::Pin;
use std::task::{Context, Poll};

use pin_project_lite::pin_project;

#[cfg(feature = "async-std")]
mod catch_unwind;

pub type Error = Box<dyn Any + Send + 'static>;
pub type Result<T, E = Error> = std::result::Result<T, E>;

pin_project! {
    pub struct JoinHandle<T> {
        // `*const T` is `Unpin` without requiring `T` to be `Unpin`, but has covariance like `T`.
        // https://doc.rust-lang.org/stable/nomicon/phantom-data.html#table-of-phantomdata-patterns

        #[pin]
        inner: FromRuntime! {
            // This won't actually be used so we just need something to satisfy the compiler.
            "std" => PhantomData<*const T>,
            "async-std" => async_std::task::JoinHandle<Result<T>>,
            "tokio" => tokio::task::JoinHandle<T>,
        },

        // in case no async runtimes are enabled, we need to mark the type parameter T as used
        phantom: PhantomData<*const T>,
    }
}

impl<T> Future for JoinHandle<T> {
    type Output = Result<T>;

    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        apply_mut_runtime_async!(self.project().inner, cx, {
            "async-std" => |handle, cx| Pin::new(handle).poll(cx),
            "tokio" => |handle, cx|
                // We don't allow cancellation right now so it couldn't be a different error.
                Pin::new(handle).poll(cx).map(|res| res.map_err(tokio::task::JoinError::into_panic)),
        })
    }
}

#[allow(unreachable_code)]
pub fn spawn<T: Future + Send + 'static>(future: T) -> JoinHandle<T::Output>
where
    T::Output: Send + 'static,
{
    let inner = pick_async_runtime!(future, {
        "async-std" => |future| async_std::task::spawn(catch_unwind::CatchUnwind::new(future)),
        "tokio" => |future| tokio::task::spawn(future),
    });

    JoinHandle {
        inner,
        phantom: PhantomData,
    }
}