go 0.1.2

A runtime-agnostic Go-style concurrency library for Rust
Documentation
use std::any::Any;
use std::fmt;
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};

use crate::backend::{Active, Backend};

// ---------------------------------------------------------------------------
// JoinError
// ---------------------------------------------------------------------------

/// Error returned when a spawned task fails.
pub enum JoinError {
    /// The task panicked.
    Panic(Box<dyn Any + Send>),
    /// The task was cancelled.
    Cancelled,
}

impl fmt::Debug for JoinError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            JoinError::Panic(_) => f.write_str("JoinError::Panic(..)"),
            JoinError::Cancelled => f.write_str("JoinError::Cancelled"),
        }
    }
}

impl fmt::Display for JoinError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            JoinError::Panic(_) => f.write_str("task panicked"),
            JoinError::Cancelled => f.write_str("task was cancelled"),
        }
    }
}

impl std::error::Error for JoinError {}

// ---------------------------------------------------------------------------
// JoinHandle<T> — inner type varies by cfg
// ---------------------------------------------------------------------------

// The raw result type that all backends produce after catch_unwind.
type CatchUnwindResult<T> = Result<T, Box<dyn Any + Send>>;

/// A handle to a spawned task. Dropping it detaches the task (fire-and-forget).
pub struct JoinHandle<T> {
    #[cfg(feature = "rt-tokio")]
    pub(crate) inner: tokio::task::JoinHandle<CatchUnwindResult<T>>,

    #[cfg(feature = "rt-async-std")]
    pub(crate) inner: Option<async_std::task::JoinHandle<CatchUnwindResult<T>>>,

    #[cfg(feature = "rt-smol")]
    pub(crate) inner: Option<smol::Task<CatchUnwindResult<T>>>,
}

// All backend handle types (tokio::task::JoinHandle, async_std::task::JoinHandle,
// smol::Task) implement Unpin, so JoinHandle<T> is Unpin as well.
impl<T> Unpin for JoinHandle<T> {}

// ---------------------------------------------------------------------------
// Future impl for JoinHandle<T>
// ---------------------------------------------------------------------------

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

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

        #[cfg(feature = "rt-tokio")]
        {
            // tokio::task::JoinHandle is Unpin
            match Pin::new(&mut this.inner).poll(cx) {
                Poll::Ready(Ok(Ok(val))) => Poll::Ready(Ok(val)),
                Poll::Ready(Ok(Err(panic_payload))) => {
                    Poll::Ready(Err(JoinError::Panic(panic_payload)))
                }
                Poll::Ready(Err(_join_err)) => Poll::Ready(Err(JoinError::Cancelled)),
                Poll::Pending => Poll::Pending,
            }
        }

        #[cfg(feature = "rt-async-std")]
        {
            let handle = this
                .inner
                .as_mut()
                .expect("JoinHandle polled after completion");
            match Pin::new(handle).poll(cx) {
                Poll::Ready(Ok(val)) => {
                    this.inner = None;
                    Poll::Ready(Ok(val))
                }
                Poll::Ready(Err(panic_payload)) => {
                    this.inner = None;
                    Poll::Ready(Err(JoinError::Panic(panic_payload)))
                }
                Poll::Pending => Poll::Pending,
            }
        }

        #[cfg(feature = "rt-smol")]
        {
            let task = this
                .inner
                .as_mut()
                .expect("JoinHandle polled after completion");
            match Pin::new(task).poll(cx) {
                Poll::Ready(Ok(val)) => {
                    this.inner = None;
                    Poll::Ready(Ok(val))
                }
                Poll::Ready(Err(panic_payload)) => {
                    this.inner = None;
                    Poll::Ready(Err(JoinError::Panic(panic_payload)))
                }
                Poll::Pending => Poll::Pending,
            }
        }
    }
}

// Drop: detach semantics (fire-and-forget, matching Go goroutines)
// tokio: native handle already detaches on drop — nothing needed.
// async-std: native JoinHandle already detaches on drop — nothing needed.
// smol: Task cancels on drop by default — we must explicitly detach.
#[cfg(feature = "rt-smol")]
impl<T> Drop for JoinHandle<T> {
    fn drop(&mut self) {
        if let Some(task) = self.inner.take() {
            task.detach();
        }
    }
}

// ---------------------------------------------------------------------------
// spawn()
// ---------------------------------------------------------------------------

/// Spawn an async task, returning a handle that can be awaited for the result.
///
/// If the task panics, the panic is captured and returned as `JoinError::Panic`.
/// Dropping the handle detaches the task (it keeps running in the background).
pub fn spawn<T: Send + 'static>(fut: impl Future<Output = T> + Send + 'static) -> JoinHandle<T> {
    Active::spawn(fut)
}

// ---------------------------------------------------------------------------
// go! macro
// ---------------------------------------------------------------------------

/// Spawn an async task (Go-style). Returns a `JoinHandle<T>`.
///
/// # Example
/// ```ignore
/// let h = go::go!(async { 1 + 1 });
/// assert_eq!(h.await.unwrap(), 2);
/// ```
#[macro_export]
macro_rules! go {
    ($expr:expr) => {
        $crate::spawn($expr)
    };
}