# `go` Concurrency Library Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Implement a runtime-agnostic Go-style concurrency library with spawn, channels, select, WaitGroup, and timers, switchable between tokio/async-std/smol via compile-time features.

**Architecture:** A `Backend` trait with compile-time-selected concrete type (static dispatch). Runtime-specific code (spawn, sleep) lives behind the trait; runtime-agnostic primitives (chan, WaitGroup, select, timeout) are independent. Zero runtime overhead via monomorphization.

**Tech Stack:** Rust (edition 2024), async-channel, event-listener, futures-lite, pin-project-lite, tokio/async-std/smol (cfg-gated).

---

## File Structure

| File | Responsibility |
|------|----------------|
| `Cargo.toml` | Dependencies, features, metadata |
| `src/lib.rs` | Feature guard, public re-exports, crate docs |
| `src/backend/mod.rs` | `Backend` trait definition + `Active` type alias |
| `src/backend/tokio.rs` | `TokioBackend` impl |
| `src/backend/async_std.rs` | `AsyncStdBackend` impl |
| `src/backend/smol.rs` | `SmolBackend` impl |
| `src/spawn.rs` | `go!` macro, `spawn()`, `JoinHandle<T>`, `JoinError` |
| `src/time.rs` | `sleep()`, `timeout()`, `Elapsed` |
| `src/chan.rs` | `bounded()`, `unbounded()`, `Sender<T>`, `Receiver<T>` |
| `src/waitgroup.rs` | `WaitGroup`, `WaitGroupGuard` |
| `src/select.rs` | `select!` macro |
| `tests/integration.rs` | Shared integration tests (run under each feature) |

---

### Task 1: Project Scaffold and Feature Guard

**Files:**
- Modify: `Cargo.toml`
- Modify: `src/lib.rs`

- [ ] **Step 1: Update Cargo.toml with dependencies and features**

```toml
[package]
name = "go"
version = "0.1.0"
edition = "2024"
description = "A runtime-agnostic Go-style concurrency library for Rust"
license = "MIT OR Apache-2.0"

[features]
default = ["rt-tokio"]
rt-tokio = ["dep:tokio"]
rt-async-std = ["dep:async-std"]
rt-smol = ["dep:smol"]

[dependencies]
async-channel = "2"
event-listener = "5"
futures-lite = "2"
pin-project-lite = "0.2"

tokio = { version = "1", features = ["rt", "time"], optional = true }
async-std = { version = "1", optional = true }
smol = { version = "2", optional = true }

[dev-dependencies]
tokio = { version = "1", features = ["rt", "rt-multi-thread", "time", "macros"] }
```

- [ ] **Step 2: Write lib.rs with feature guard and module declarations**

```rust
// src/lib.rs

#[cfg(not(any(feature = "rt-tokio", feature = "rt-async-std", feature = "rt-smol")))]
compile_error!("go: exactly one runtime feature must be enabled (rt-tokio, rt-async-std, or rt-smol)");

#[cfg(any(
    all(feature = "rt-tokio", feature = "rt-async-std"),
    all(feature = "rt-tokio", feature = "rt-smol"),
    all(feature = "rt-async-std", feature = "rt-smol"),
))]
compile_error!("go: exactly one runtime feature must be enabled — multiple selected");

mod backend;
mod spawn;
mod time;
pub mod chan;
mod waitgroup;
mod select;

pub use spawn::{spawn, JoinHandle, JoinError};
pub use time::{sleep, timeout, Elapsed};
pub use waitgroup::{WaitGroup, WaitGroupGuard};
```

- [ ] **Step 3: Create empty module files so the crate compiles**

Create `src/backend/mod.rs`, `src/backend/tokio.rs`, `src/backend/async_std.rs`, `src/backend/smol.rs`, `src/spawn.rs`, `src/time.rs`, `src/chan.rs`, `src/waitgroup.rs`, `src/select.rs` — each with just enough content to not produce compile errors (empty or with placeholder struct/fn signatures that will be filled in subsequent tasks).

Minimal contents for each:

`src/backend/mod.rs`:
```rust
use std::future::Future;
use std::time::Duration;

pub(crate) trait Backend {
    fn spawn<T: Send + 'static>(fut: impl Future<Output = T> + Send + 'static) -> crate::JoinHandle<T>;
    fn sleep(dur: Duration) -> impl Future<Output = ()> + Send;
}

#[cfg(feature = "rt-tokio")]
mod tokio_backend;
#[cfg(feature = "rt-tokio")]
pub(crate) type Active = tokio_backend::TokioBackend;

#[cfg(feature = "rt-async-std")]
mod async_std_backend;
#[cfg(feature = "rt-async-std")]
pub(crate) type Active = async_std_backend::AsyncStdBackend;

#[cfg(feature = "rt-smol")]
mod smol_backend;
#[cfg(feature = "rt-smol")]
pub(crate) type Active = smol_backend::SmolBackend;
```

`src/backend/tokio_backend.rs`:
```rust
pub(crate) struct TokioBackend;
```

`src/backend/async_std_backend.rs`:
```rust
pub(crate) struct AsyncStdBackend;
```

`src/backend/smol_backend.rs`:
```rust
pub(crate) struct SmolBackend;
```

`src/spawn.rs`:
```rust
use std::any::Any;

pub struct JoinHandle<T> {
    _marker: std::marker::PhantomData<T>,
}

pub enum JoinError {
    Panic(Box<dyn Any + Send>),
    Cancelled,
}

pub fn spawn<T: Send + 'static>(_fut: impl std::future::Future<Output = T> + Send + 'static) -> JoinHandle<T> {
    todo!()
}
```

`src/time.rs`:
```rust
use std::time::Duration;
use std::future::Future;

pub struct Elapsed;

pub async fn sleep(_dur: Duration) {
    todo!()
}

pub async fn timeout<T, F: Future<Output = T>>(_dur: Duration, _fut: F) -> Result<T, Elapsed> {
    todo!()
}
```

`src/chan.rs`:
```rust
pub use async_channel::{Sender, Receiver, SendError, RecvError};

pub fn bounded<T>(cap: usize) -> (Sender<T>, Receiver<T>) {
    async_channel::bounded(cap)
}

pub fn unbounded<T>() -> (Sender<T>, Receiver<T>) {
    async_channel::unbounded()
}
```

`src/waitgroup.rs`:
```rust
pub struct WaitGroup;
pub struct WaitGroupGuard;
```

`src/select.rs`:
```rust
// select! macro — implemented in Task 7
```

- [ ] **Step 4: Verify it compiles under default feature**

Run: `cargo check`
Expected: compiles with no errors (there will be dead_code warnings, which is fine)

- [ ] **Step 5: Commit**

```bash
git add -A
git commit -m "feat: scaffold project with features, deps, and module structure"
```

---

### Task 2: Backend Trait and Tokio Implementation

**Files:**
- Modify: `src/backend/mod.rs`
- Modify: `src/backend/tokio_backend.rs`
- Modify: `src/spawn.rs`

- [ ] **Step 1: Write a failing test for tokio spawn**

Create `tests/integration.rs`:
```rust
#[cfg(feature = "rt-tokio")]
#[tokio::test]
async fn spawn_returns_value() {
    let handle = go::spawn(async { 42 });
    let result = handle.await.unwrap();
    assert_eq!(result, 42);
}
```

- [ ] **Step 2: Run test to verify it fails**

Run: `cargo test --test integration --features rt-tokio`
Expected: FAIL (spawn calls `todo!()`)

- [ ] **Step 3: Implement JoinHandle and JoinError properly**

`src/spawn.rs`:
```rust
use std::any::Any;
use std::fmt;
use std::future::Future;
use std::panic::AssertUnwindSafe;
use std::pin::Pin;
use std::task::{Context, Poll};

use futures_lite::FutureExt;
use pin_project_lite::pin_project;

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

pub enum JoinError {
    Panic(Box<dyn Any + Send>),
    Cancelled,
}

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

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

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

pin_project! {
    pub struct JoinHandle<T> {
        #[pin]
        inner: JoinHandleInner<T>,
    }
}

#[cfg(feature = "rt-tokio")]
type JoinHandleInner<T> = tokio::task::JoinHandle<Result<T, Box<dyn Any + Send>>>;

#[cfg(feature = "rt-async-std")]
type JoinHandleInner<T> = async_std::task::JoinHandle<Result<T, Box<dyn Any + Send>>>;

#[cfg(feature = "rt-smol")]
pin_project! {
    struct SmolInner<T> {
        task: Option<smol::Task<Result<T, Box<dyn Any + Send>>>>,
    }
}

#[cfg(feature = "rt-smol")]
type JoinHandleInner<T> = SmolInner<T>;

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

    #[cfg(feature = "rt-tokio")]
    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        let this = self.project();
        match 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")]
    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        let this = self.project();
        match this.inner.poll(cx) {
            Poll::Ready(Ok(val)) => Poll::Ready(Ok(val)),
            Poll::Ready(Err(panic_payload)) => Poll::Ready(Err(JoinError::Panic(panic_payload))),
            Poll::Pending => Poll::Pending,
        }
    }

    #[cfg(feature = "rt-smol")]
    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        let this = self.project();
        let inner = this.inner.project();
        let task = inner.task.as_mut().expect("polled after completion");
        let pinned = unsafe { Pin::new_unchecked(task) };
        match pinned.poll(cx) {
            Poll::Ready(Ok(val)) => {
                // take to prevent double-poll
                Poll::Ready(Ok(val))
            }
            Poll::Ready(Err(panic_payload)) => Poll::Ready(Err(JoinError::Panic(panic_payload))),
            Poll::Pending => Poll::Pending,
        }
    }
}

#[cfg(feature = "rt-smol")]
impl<T> Drop for JoinHandle<T> {
    fn drop(&mut self) {
        if let Some(task) = self.inner.task.take() {
            task.detach();
        }
    }
}

pub fn spawn<T: Send + 'static>(fut: impl Future<Output = T> + Send + 'static) -> JoinHandle<T> {
    Active::spawn(fut)
}

#[macro_export]
macro_rules! go {
    ($fut:expr) => {
        $crate::spawn($fut)
    };
}
```

- [ ] **Step 4: Implement TokioBackend**

`src/backend/tokio_backend.rs`:
```rust
use std::any::Any;
use std::future::Future;
use std::panic::AssertUnwindSafe;
use std::time::Duration;

use futures_lite::FutureExt;

use super::Backend;
use crate::JoinHandle;

pub(crate) struct TokioBackend;

impl Backend for TokioBackend {
    fn spawn<T: Send + 'static>(fut: impl Future<Output = T> + Send + 'static) -> JoinHandle<T> {
        let handle = tokio::task::spawn(AssertUnwindSafe(fut).catch_unwind());
        JoinHandle { inner: handle }
    }

    fn sleep(dur: Duration) -> impl Future<Output = ()> + Send {
        tokio::time::sleep(dur)
    }
}
```

- [ ] **Step 5: Run test to verify it passes**

Run: `cargo test --test integration --features rt-tokio`
Expected: PASS

- [ ] **Step 6: Commit**

```bash
git add -A
git commit -m "feat: implement Backend trait and tokio spawn/sleep"
```

---

### Task 3: async-std and smol Backend Implementations

**Files:**
- Modify: `src/backend/async_std_backend.rs`
- Modify: `src/backend/smol_backend.rs`
- Modify: `tests/integration.rs`

- [ ] **Step 1: Add integration tests for async-std and smol**

Append to `tests/integration.rs`:
```rust
#[cfg(feature = "rt-async-std")]
#[async_std::test]
async fn spawn_returns_value() {
    let handle = go::spawn(async { 42 });
    let result = handle.await.unwrap();
    assert_eq!(result, 42);
}

#[cfg(feature = "rt-smol")]
#[test]
fn spawn_returns_value() {
    smol::block_on(async {
        let handle = go::spawn(async { 42 });
        let result = handle.await.unwrap();
        assert_eq!(result, 42);
    });
}
```

- [ ] **Step 2: Implement AsyncStdBackend**

`src/backend/async_std_backend.rs`:
```rust
use std::future::Future;
use std::panic::AssertUnwindSafe;
use std::time::Duration;

use futures_lite::FutureExt;

use super::Backend;
use crate::JoinHandle;

pub(crate) struct AsyncStdBackend;

impl Backend for AsyncStdBackend {
    fn spawn<T: Send + 'static>(fut: impl Future<Output = T> + Send + 'static) -> JoinHandle<T> {
        let handle = async_std::task::spawn(AssertUnwindSafe(fut).catch_unwind());
        JoinHandle { inner: handle }
    }

    fn sleep(dur: Duration) -> impl Future<Output = ()> + Send {
        async_std::task::sleep(dur)
    }
}
```

- [ ] **Step 3: Implement SmolBackend**

`src/backend/smol_backend.rs`:
```rust
use std::future::Future;
use std::panic::AssertUnwindSafe;
use std::time::Duration;

use futures_lite::FutureExt;

use super::Backend;
use crate::spawn::SmolInner;
use crate::JoinHandle;

pub(crate) struct SmolBackend;

impl Backend for SmolBackend {
    fn spawn<T: Send + 'static>(fut: impl Future<Output = T> + Send + 'static) -> JoinHandle<T> {
        let task = smol::spawn(AssertUnwindSafe(fut).catch_unwind());
        JoinHandle {
            inner: SmolInner { task: Some(task) },
        }
    }

    fn sleep(dur: Duration) -> impl Future<Output = ()> + Send {
        smol::Timer::after(dur)
    }
}
```

- [ ] **Step 4: Update dev-dependencies for testing all backends**

Add to `Cargo.toml` `[dev-dependencies]`:
```toml
async-std = { version = "1", features = ["attributes"] }
smol = "2"
```

- [ ] **Step 5: Test all three backends**

Run: `cargo test --test integration --no-default-features --features rt-tokio`
Run: `cargo test --test integration --no-default-features --features rt-async-std`
Run: `cargo test --test integration --no-default-features --features rt-smol`
Expected: all PASS

- [ ] **Step 6: Commit**

```bash
git add -A
git commit -m "feat: implement async-std and smol backends"
```

---

### Task 4: Channel (chan) Module

**Files:**
- Modify: `src/chan.rs`
- Modify: `tests/integration.rs`

- [ ] **Step 1: Write failing channel tests**

Append to `tests/integration.rs`:
```rust
#[cfg(feature = "rt-tokio")]
mod chan_tests {
    #[tokio::test]
    async fn bounded_send_recv() {
        let (tx, rx) = go::chan::bounded::<i32>(4);
        tx.send(10).await.unwrap();
        tx.send(20).await.unwrap();
        assert_eq!(rx.recv().await.unwrap(), 10);
        assert_eq!(rx.recv().await.unwrap(), 20);
    }

    #[tokio::test]
    async fn unbounded_send_recv() {
        let (tx, rx) = go::chan::unbounded::<String>();
        tx.send("hello".into()).await.unwrap();
        assert_eq!(rx.recv().await.unwrap(), "hello");
    }

    #[tokio::test]
    async fn recv_error_on_closed() {
        let (tx, rx) = go::chan::bounded::<i32>(1);
        drop(tx);
        assert!(rx.recv().await.is_err());
    }
}
```

- [ ] **Step 2: Run tests to verify they fail**

Run: `cargo test --test integration --features rt-tokio`
Expected: compilation error or test failure (chan module is minimal)

- [ ] **Step 3: Implement chan module**

`src/chan.rs`:
```rust
pub use async_channel::{Sender, Receiver, SendError, RecvError};

/// Create a bounded channel with the given capacity (must be ≥ 1).
pub fn bounded<T>(cap: usize) -> (Sender<T>, Receiver<T>) {
    assert!(cap >= 1, "go::chan::bounded requires cap >= 1");
    async_channel::bounded(cap)
}

/// Create an unbounded channel.
pub fn unbounded<T>() -> (Sender<T>, Receiver<T>) {
    async_channel::unbounded()
}
```

- [ ] **Step 4: Run tests to verify they pass**

Run: `cargo test --test integration --features rt-tokio`
Expected: PASS

- [ ] **Step 5: Commit**

```bash
git add -A
git commit -m "feat: implement chan module (bounded/unbounded channels)"
```

---

### Task 5: WaitGroup

**Files:**
- Modify: `src/waitgroup.rs`
- Modify: `tests/integration.rs`

- [ ] **Step 1: Write failing WaitGroup tests**

Append to `tests/integration.rs`:
```rust
#[cfg(feature = "rt-tokio")]
mod waitgroup_tests {
    use go::WaitGroup;

    #[tokio::test]
    async fn wait_completes_when_all_done() {
        let wg = WaitGroup::new();
        wg.add(3);

        for _ in 0..3 {
            let wg = wg.clone();
            go::go!(async move {
                wg.done();
            });
        }

        wg.wait().await;
    }

    #[tokio::test]
    async fn guard_calls_done_on_drop() {
        let wg = WaitGroup::new();
        wg.add(1);

        go::go!(async move {
            let _guard = wg.guard();
            // guard drops here, calling done()
        });

        // We need a fresh clone to wait on
        // Actually the API should allow wait on the original
    }

    #[tokio::test]
    async fn wait_with_zero_is_immediate() {
        let wg = WaitGroup::new();
        wg.wait().await;
    }
}
```

- [ ] **Step 2: Run tests to verify they fail**

Run: `cargo test --test integration --features rt-tokio`
Expected: FAIL (WaitGroup is an empty struct)

- [ ] **Step 3: Implement WaitGroup**

`src/waitgroup.rs`:
```rust
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;

use event_listener::Event;

struct Inner {
    count: AtomicUsize,
    event: Event,
}

#[derive(Clone)]
pub struct WaitGroup {
    inner: Arc<Inner>,
}

impl WaitGroup {
    pub fn new() -> Self {
        Self {
            inner: Arc::new(Inner {
                count: AtomicUsize::new(0),
                event: Event::new(),
            }),
        }
    }

    pub fn add(&self, n: usize) {
        self.inner.count.fetch_add(n, Ordering::AcqRel);
    }

    pub fn done(&self) {
        let prev = self.inner.count.fetch_sub(1, Ordering::AcqRel);
        if prev == 1 {
            self.inner.event.notify(usize::MAX);
        }
    }

    pub async fn wait(&self) {
        loop {
            if self.inner.count.load(Ordering::Acquire) == 0 {
                return;
            }
            let listener = self.inner.event.listen();
            if self.inner.count.load(Ordering::Acquire) == 0 {
                return;
            }
            listener.await;
        }
    }

    pub fn guard(&self) -> WaitGroupGuard {
        WaitGroupGuard { wg: self.clone() }
    }
}

impl Default for WaitGroup {
    fn default() -> Self {
        Self::new()
    }
}

pub struct WaitGroupGuard {
    wg: WaitGroup,
}

impl Drop for WaitGroupGuard {
    fn drop(&mut self) {
        self.wg.done();
    }
}
```

- [ ] **Step 4: Fix test to properly test guard**

Update the guard test in `tests/integration.rs`:
```rust
    #[tokio::test]
    async fn guard_calls_done_on_drop() {
        let wg = WaitGroup::new();
        wg.add(1);
        let wg2 = wg.clone();

        go::go!(async move {
            let _guard = wg2.guard();
        });

        wg.wait().await;
    }
```

- [ ] **Step 5: Run tests to verify they pass**

Run: `cargo test --test integration --features rt-tokio`
Expected: PASS

- [ ] **Step 6: Commit**

```bash
git add -A
git commit -m "feat: implement WaitGroup with guard"
```

---

### Task 6: Time (sleep / timeout)

**Files:**
- Modify: `src/time.rs`
- Modify: `tests/integration.rs`

- [ ] **Step 1: Write failing time tests**

Append to `tests/integration.rs`:
```rust
#[cfg(feature = "rt-tokio")]
mod time_tests {
    use std::time::{Duration, Instant};

    #[tokio::test]
    async fn sleep_completes() {
        let start = Instant::now();
        go::sleep(Duration::from_millis(50)).await;
        assert!(start.elapsed() >= Duration::from_millis(40));
    }

    #[tokio::test]
    async fn timeout_succeeds_when_fast() {
        let result = go::timeout(Duration::from_secs(1), async { 42 }).await;
        assert_eq!(result.unwrap(), 42);
    }

    #[tokio::test]
    async fn timeout_fails_when_slow() {
        let result = go::timeout(Duration::from_millis(10), async {
            go::sleep(Duration::from_secs(10)).await;
            42
        })
        .await;
        assert!(result.is_err());
    }
}
```

- [ ] **Step 2: Run tests to verify they fail**

Run: `cargo test --test integration --features rt-tokio`
Expected: FAIL (sleep/timeout call `todo!()`)

- [ ] **Step 3: Implement time module**

`src/time.rs`:
```rust
use std::fmt;
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
use std::time::Duration;

use futures_lite::FutureExt;
use pin_project_lite::pin_project;

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

#[derive(Debug, Clone, Copy)]
pub struct Elapsed;

impl fmt::Display for Elapsed {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str("deadline elapsed")
    }
}

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

pub async fn sleep(dur: Duration) {
    Active::sleep(dur).await
}

pub async fn timeout<T, F: Future<Output = T>>(dur: Duration, fut: F) -> Result<T, Elapsed> {
    futures_lite::future::or(async { Ok(fut.await) }, async {
        sleep(dur).await;
        Err(Elapsed)
    })
    .await
}
```

- [ ] **Step 4: Run tests to verify they pass**

Run: `cargo test --test integration --features rt-tokio`
Expected: PASS

- [ ] **Step 5: Test under all backends**

Run: `cargo test --test integration --no-default-features --features rt-async-std`
Run: `cargo test --test integration --no-default-features --features rt-smol`
Expected: PASS (add backend-specific test wrappers if needed)

- [ ] **Step 6: Commit**

```bash
git add -A
git commit -m "feat: implement sleep and timeout"
```

---

### Task 7: select! Macro

**Files:**
- Modify: `src/select.rs`
- Modify: `tests/integration.rs`

- [ ] **Step 1: Write failing select tests**

Append to `tests/integration.rs`:
```rust
#[cfg(feature = "rt-tokio")]
mod select_tests {
    #[tokio::test]
    async fn select_picks_ready_branch() {
        let (tx1, rx1) = go::chan::bounded::<i32>(1);
        let (_tx2, rx2) = go::chan::bounded::<i32>(1);

        tx1.send(42).await.unwrap();

        let val = go::select! {
            v = rx1.recv() => v.unwrap(),
            v = rx2.recv() => v.unwrap(),
        };
        assert_eq!(val, 42);
    }

    #[tokio::test]
    async fn select_default_when_none_ready() {
        let (_tx, rx) = go::chan::bounded::<i32>(1);

        let val = go::select! {
            v = rx.recv() => v.unwrap(),
            default => -1,
        };
        assert_eq!(val, -1);
    }
}
```

- [ ] **Step 2: Run tests to verify they fail**

Run: `cargo test --test integration --features rt-tokio`
Expected: FAIL (select! not defined)

- [ ] **Step 3: Implement select! macro**

`src/select.rs`:
```rust
/// Select on multiple channel recv operations. Picks the first branch that is
/// ready. If none are ready and a `default` branch is provided, runs default.
/// Otherwise waits until one becomes ready.
///
/// Usage:
/// ```ignore
/// go::select! {
///     val = rx1.recv() => { /* use val */ },
///     val = rx2.recv() => { /* use val */ },
///     default => { /* none ready */ },
/// }
/// ```
#[macro_export]
macro_rules! select {
    // Internal: parse branches and optional default
    (@ [$($arms:tt)*] default => $default:expr $(,)?) => {
        $crate::__select_impl!($($arms)* ; default { $default })
    };
    (@ [$($arms:tt)*] $var:pat = $fut:expr => $body:expr, $($rest:tt)*) => {
        $crate::select!(@ [$($arms)* $var = $fut => $body,] $($rest)*)
    };
    (@ [$($arms:tt)*] $var:pat = $fut:expr => $body:expr $(,)?) => {
        $crate::__select_impl!($($arms)* $var = $fut => $body, ; no_default)
    };
    // Entry point
    ($($input:tt)*) => {
        $crate::select!(@ [] $($input)*)
    };
}

#[macro_export]
#[doc(hidden)]
macro_rules! __select_impl {
    // Two branches + default
    ($v1:pat = $f1:expr => $b1:expr, $v2:pat = $f2:expr => $b2:expr, ; default { $default:expr }) => {{
        use ::futures_lite::FutureExt;
        let mut f1 = core::pin::pin!($f1);
        let mut f2 = core::pin::pin!($f2);
        ::futures_lite::future::poll_fn(|cx| {
            if let core::task::Poll::Ready(v) = f1.as_mut().poll(cx) {
                let $v1 = v;
                return core::task::Poll::Ready($b1);
            }
            if let core::task::Poll::Ready(v) = f2.as_mut().poll(cx) {
                let $v2 = v;
                return core::task::Poll::Ready($b2);
            }
            core::task::Poll::Ready($default)
        }).await
    }};
    // Two branches, no default (wait)
    ($v1:pat = $f1:expr => $b1:expr, $v2:pat = $f2:expr => $b2:expr, ; no_default) => {{
        use ::futures_lite::FutureExt;
        let mut f1 = core::pin::pin!($f1);
        let mut f2 = core::pin::pin!($f2);
        ::futures_lite::future::poll_fn(|cx| {
            if let core::task::Poll::Ready(v) = f1.as_mut().poll(cx) {
                let $v1 = v;
                return core::task::Poll::Ready($b1);
            }
            if let core::task::Poll::Ready(v) = f2.as_mut().poll(cx) {
                let $v2 = v;
                return core::task::Poll::Ready($b2);
            }
            core::task::Poll::Pending
        }).await
    }};
    // Three branches + default
    ($v1:pat = $f1:expr => $b1:expr, $v2:pat = $f2:expr => $b2:expr, $v3:pat = $f3:expr => $b3:expr, ; default { $default:expr }) => {{
        use ::futures_lite::FutureExt;
        let mut f1 = core::pin::pin!($f1);
        let mut f2 = core::pin::pin!($f2);
        let mut f3 = core::pin::pin!($f3);
        ::futures_lite::future::poll_fn(|cx| {
            if let core::task::Poll::Ready(v) = f1.as_mut().poll(cx) {
                let $v1 = v;
                return core::task::Poll::Ready($b1);
            }
            if let core::task::Poll::Ready(v) = f2.as_mut().poll(cx) {
                let $v2 = v;
                return core::task::Poll::Ready($b2);
            }
            if let core::task::Poll::Ready(v) = f3.as_mut().poll(cx) {
                let $v3 = v;
                return core::task::Poll::Ready($b3);
            }
            core::task::Poll::Ready($default)
        }).await
    }};
    // Three branches, no default
    ($v1:pat = $f1:expr => $b1:expr, $v2:pat = $f2:expr => $b2:expr, $v3:pat = $f3:expr => $b3:expr, ; no_default) => {{
        use ::futures_lite::FutureExt;
        let mut f1 = core::pin::pin!($f1);
        let mut f2 = core::pin::pin!($f2);
        let mut f3 = core::pin::pin!($f3);
        ::futures_lite::future::poll_fn(|cx| {
            if let core::task::Poll::Ready(v) = f1.as_mut().poll(cx) {
                let $v1 = v;
                return core::task::Poll::Ready($b1);
            }
            if let core::task::Poll::Ready(v) = f2.as_mut().poll(cx) {
                let $v2 = v;
                return core::task::Poll::Ready($b2);
            }
            if let core::task::Poll::Ready(v) = f3.as_mut().poll(cx) {
                let $v3 = v;
                return core::task::Poll::Ready($b3);
            }
            core::task::Poll::Pending
        }).await
    }};
    // Single branch + default
    ($v1:pat = $f1:expr => $b1:expr, ; default { $default:expr }) => {{
        use ::futures_lite::FutureExt;
        let mut f1 = core::pin::pin!($f1);
        ::futures_lite::future::poll_fn(|cx| {
            if let core::task::Poll::Ready(v) = f1.as_mut().poll(cx) {
                let $v1 = v;
                return core::task::Poll::Ready($b1);
            }
            core::task::Poll::Ready($default)
        }).await
    }};
    // Single branch, no default
    ($v1:pat = $f1:expr => $b1:expr, ; no_default) => {{
        let $v1 = $f1.await;
        $b1
    }};
}
```

- [ ] **Step 4: Run tests to verify they pass**

Run: `cargo test --test integration --features rt-tokio`
Expected: PASS

- [ ] **Step 5: Commit**

```bash
git add -A
git commit -m "feat: implement select! macro for channel recv multiplexing"
```

---

### Task 8: Cross-Runtime Integration Test Suite

**Files:**
- Modify: `tests/integration.rs`

- [ ] **Step 1: Refactor integration tests to work under all three backends**

Restructure `tests/integration.rs` so that the same logical tests compile under each backend. Use a helper macro to avoid tripling the test code:

```rust
// tests/integration.rs

// Helper: each backend needs its own async test runner attribute/wrapper.
// We define tests in a module and gate the runner.

macro_rules! run_async {
    ($body:expr) => {{
        #[cfg(feature = "rt-tokio")]
        {
            tokio::runtime::Runtime::new().unwrap().block_on($body)
        }
        #[cfg(feature = "rt-async-std")]
        {
            async_std::task::block_on($body)
        }
        #[cfg(feature = "rt-smol")]
        {
            smol::block_on($body)
        }
    }};
}

#[test]
fn spawn_returns_value() {
    run_async!(async {
        let handle = go::spawn(async { 42 });
        assert_eq!(handle.await.unwrap(), 42);
    });
}

#[test]
fn spawn_panic_captured() {
    run_async!(async {
        let handle = go::spawn(async { panic!("oops") as i32 });
        let err = handle.await.unwrap_err();
        assert!(matches!(err, go::JoinError::Panic(_)));
    });
}

#[test]
fn chan_bounded_send_recv() {
    run_async!(async {
        let (tx, rx) = go::chan::bounded(4);
        tx.send(10).await.unwrap();
        tx.send(20).await.unwrap();
        assert_eq!(rx.recv().await.unwrap(), 10);
        assert_eq!(rx.recv().await.unwrap(), 20);
    });
}

#[test]
fn chan_unbounded_send_recv() {
    run_async!(async {
        let (tx, rx) = go::chan::unbounded();
        tx.send("hello".to_string()).await.unwrap();
        assert_eq!(rx.recv().await.unwrap(), "hello");
    });
}

#[test]
fn chan_recv_error_on_closed() {
    run_async!(async {
        let (tx, rx) = go::chan::bounded::<i32>(1);
        drop(tx);
        assert!(rx.recv().await.is_err());
    });
}

#[test]
fn waitgroup_basic() {
    run_async!(async {
        let wg = go::WaitGroup::new();
        wg.add(3);
        for _ in 0..3 {
            let wg = wg.clone();
            go::go!(async move { wg.done(); });
        }
        wg.wait().await;
    });
}

#[test]
fn waitgroup_guard() {
    run_async!(async {
        let wg = go::WaitGroup::new();
        wg.add(1);
        let wg2 = wg.clone();
        go::go!(async move { let _g = wg2.guard(); });
        wg.wait().await;
    });
}

#[test]
fn sleep_works() {
    run_async!(async {
        let start = std::time::Instant::now();
        go::sleep(std::time::Duration::from_millis(50)).await;
        assert!(start.elapsed() >= std::time::Duration::from_millis(40));
    });
}

#[test]
fn timeout_ok() {
    run_async!(async {
        let r = go::timeout(std::time::Duration::from_secs(1), async { 99 }).await;
        assert_eq!(r.unwrap(), 99);
    });
}

#[test]
fn timeout_elapsed() {
    run_async!(async {
        let r = go::timeout(std::time::Duration::from_millis(10), async {
            go::sleep(std::time::Duration::from_secs(10)).await;
            0
        }).await;
        assert!(r.is_err());
    });
}

#[test]
fn select_picks_ready() {
    run_async!(async {
        let (tx1, rx1) = go::chan::bounded(1);
        let (_tx2, rx2) = go::chan::bounded::<i32>(1);
        tx1.send(42).await.unwrap();
        let val = go::select! {
            v = rx1.recv() => v.unwrap(),
            v = rx2.recv() => v.unwrap(),
        };
        assert_eq!(val, 42);
    });
}

#[test]
fn select_default_when_none_ready() {
    run_async!(async {
        let (_tx, rx) = go::chan::bounded::<i32>(1);
        let val = go::select! {
            v = rx.recv() => v.unwrap(),
            default => -1,
        };
        assert_eq!(val, -1);
    });
}
```

- [ ] **Step 2: Run under all three backends**

Run: `cargo test --test integration --no-default-features --features rt-tokio`
Run: `cargo test --test integration --no-default-features --features rt-async-std`
Run: `cargo test --test integration --no-default-features --features rt-smol`
Expected: all PASS

- [ ] **Step 3: Commit**

```bash
git add -A
git commit -m "test: unified cross-runtime integration test suite"
```

---

### Task 9: Feature Guard Negative Compile Tests

**Files:**
- Create: `tests/compile_fail/no_backend.rs`
- Create: `tests/compile_fail/multi_backend.rs`
- Modify: `Cargo.toml` (add trybuild dev-dependency)

- [ ] **Step 1: Add trybuild dev-dependency**

Add to `[dev-dependencies]` in `Cargo.toml`:
```toml
trybuild = "1"
```

- [ ] **Step 2: Create compile-fail test files**

`tests/compile_fail/no_backend.rs`:
```rust
// This should fail to compile because no backend feature is selected.
fn main() {
    let _ = go::spawn(async { 1 });
}
```

`tests/compile_fail/multi_backend.rs`:
```rust
// This should fail to compile because multiple backend features are selected.
fn main() {
    let _ = go::spawn(async { 1 });
}
```

- [ ] **Step 3: Create trybuild test runner**

Create `tests/compile_tests.rs`:
```rust
#[test]
fn compile_fail_tests() {
    let t = trybuild::TestCases::new();
    t.compile_fail("tests/compile_fail/*.rs");
}
```

- [ ] **Step 4: Run to verify correct behavior**

Run: `cargo test --test compile_tests --features rt-tokio`
Expected: trybuild confirms both files fail to compile with the expected error messages.

Note: trybuild tests require `.stderr` files to match against. On first run, use `TRYBUILD=overwrite cargo test --test compile_tests` to generate them, then commit.

- [ ] **Step 5: Commit**

```bash
git add -A
git commit -m "test: add compile-fail tests for feature guard"
```

---

### Task 10: Documentation and Cleanup

**Files:**
- Modify: `src/lib.rs` (crate-level docs)
- Modify: `Cargo.toml` (metadata)

- [ ] **Step 1: Add crate-level documentation to lib.rs**

Add at the top of `src/lib.rs` (before the feature guard):
```rust
//! # go
//!
//! A runtime-agnostic Go-style concurrency library for Rust.
//!
//! Select your async runtime at compile time via Cargo features:
//!
//! ```toml
//! [dependencies]
//! go = { version = "0.1", features = ["rt-tokio"] }   # or rt-async-std, rt-smol
//! ```
//!
//! ## Quick Start
//!
//! ```rust,no_run
//! use go::{go, spawn, chan, select, WaitGroup, sleep, timeout};
//! use std::time::Duration;
//!
//! #[tokio::main]
//! async fn main() {
//!     // Spawn a task (fire-and-forget by default)
//!     let handle = go!(async { 1 + 1 });
//!     assert_eq!(handle.await.unwrap(), 2);
//!
//!     // Channels
//!     let (tx, rx) = chan::bounded(10);
//!     tx.send(42).await.unwrap();
//!     assert_eq!(rx.recv().await.unwrap(), 42);
//! }
//! ```
```

- [ ] **Step 2: Add repository/keywords/categories to Cargo.toml**

```toml
[package]
name = "go"
version = "0.1.0"
edition = "2024"
description = "A runtime-agnostic Go-style concurrency library for Rust"
license = "MIT OR Apache-2.0"
keywords = ["async", "concurrency", "goroutine", "channel", "runtime-agnostic"]
categories = ["asynchronous", "concurrency"]
```

- [ ] **Step 3: Remove dead code warnings by checking all pub items are re-exported**

Run: `cargo clippy --features rt-tokio -- -W warnings`
Fix any warnings.

- [ ] **Step 4: Run full test suite one final time**

Run: `cargo test --no-default-features --features rt-tokio`
Run: `cargo test --no-default-features --features rt-async-std`
Run: `cargo test --no-default-features --features rt-smol`
Expected: all PASS, no warnings

- [ ] **Step 5: Commit**

```bash
git add -A
git commit -m "docs: add crate documentation and metadata"
```
