# go

[![crates.io](https://img.shields.io/crates/v/go.svg)](https://crates.io/crates/go)
[![docs.rs](https://img.shields.io/docsrs/go)](https://docs.rs/go)
[![license](https://img.shields.io/crates/l/go.svg)](#license)

A runtime-agnostic, Go-style concurrency library for Rust.

`go` gives you the concurrency primitives you reach for in Go — goroutine-style
task spawning, channels, `select`, a `WaitGroup`, timers, and `singleflight` —
on top of `async`/`await`, with the async runtime chosen at compile time.

## Why

- **Familiar.** If you know Go's `go`, `chan`, `select`, and `sync.WaitGroup`,
  you already know this API.
- **Runtime-agnostic.** Write your code once and pick the runtime
  (`tokio`, `async-std`, or `smol`) with a Cargo feature. Exactly one backend
  must be enabled — a missing or ambiguous choice is a compile error, not a
  runtime surprise.
- **Small surface.** Thin, well-tested wrappers over `async-channel` and
  `event-listener`; no heavyweight abstractions.

## Installation

```toml
[dependencies]
go = { version = "0.1", features = ["rt-tokio"] }   # or rt-async-std, rt-smol
```

`rt-tokio` is the default, so `go = "0.1"` selects Tokio. To use a different
runtime, disable defaults:

```toml
go = { version = "0.1", default-features = false, features = ["rt-async-std"] }
```

Requires a Rust toolchain with edition 2024 support.

## Quick start

```rust
use std::time::Duration;

#[tokio::main]
async fn main() {
    // Spawn a task (fire-and-forget, or await its result).
    let handle = go::spawn(async { 1 + 1 });
    assert_eq!(handle.await.unwrap(), 2);

    // Channels.
    let (tx, rx) = go::chan::bounded(8);
    go::spawn(async move {
        for i in 0..3 {
            tx.send(i).await.unwrap();
        }
    });
    while let Ok(v) = rx.recv().await {
        println!("got {v}");
    }

    // Timers.
    go::sleep(Duration::from_millis(10)).await;
}
```

## Primitives

### Spawning — `spawn` / `go!`

```rust
// These are equivalent.
let h1 = go::spawn(async { "hello" });
let h2 = go::go!(async { "hello" });

assert_eq!(h1.await.unwrap(), "hello");
```

Awaiting the returned `JoinHandle` yields `Result<T, JoinError>`; a panicking
task is reported as `JoinError::Panic`. Dropping the handle detaches the task
(it keeps running), matching goroutine semantics.

### Channels — `chan`

```rust
let (tx, rx) = go::chan::bounded(1);   // or go::chan::unbounded()
tx.send(42).await.unwrap();
assert_eq!(rx.recv().await.unwrap(), 42);
```

`Sender`/`Receiver` are clonable MPMC handles re-exported from `async-channel`.

### Multiplexing — `select!`

Wait on the first of several channel receives to complete, with an optional
non-blocking `default` branch:

```rust
go::select! {
    v = rx1.recv() => println!("from rx1: {v:?}"),
    v = rx2.recv() => println!("from rx2: {v:?}"),
    default => println!("nothing ready right now"),
}
```

Supports up to three receive branches plus the optional `default`.

### Waiting for a group — `WaitGroup`

```rust
let wg = go::WaitGroup::new();

for i in 0..4 {
    wg.add(1);
    let wg = wg.clone();
    go::spawn(async move {
        // ...do work with `i`...
        wg.done();
    });
}

wg.wait().await; // returns once every task has called done()
```

`wg.guard()` returns a guard that calls `done()` automatically when dropped.

### Timers — `sleep` / `timeout`

```rust
use std::time::Duration;

go::sleep(Duration::from_millis(50)).await;

match go::timeout(Duration::from_secs(1), do_work()).await {
    Ok(value) => { /* completed in time */ }
    Err(go::Elapsed) => { /* deadline passed */ }
}
```

### Deduplicating work — `singleflight`

Ensure only one execution is in flight for a given key; concurrent callers share
the single result (à la Go's `golang.org/x/sync/singleflight`).

```rust
let group = go::singleflight::Group::new();

// If many tasks call this concurrently with the same key, the closure runs
// once and every caller gets a clone of that one result.
let (value, shared) = group
    .do_("user:42", async { fetch_user(42).await })
    .await;

// `shared` is true if the value was shared with at least one other caller.
```

Also provides `do_chan` (run the work on the runtime and receive the result over
a channel) and `forget` (evict an in-flight key so the next call re-executes).
If the leader's future is cancelled or panics before producing a value, the
in-flight call is abandoned and waiting callers retry — no caller hangs.

## Runtime selection

| Feature         | Backend     |
| --------------- | ----------- |
| `rt-tokio`      | `tokio`     |
| `rt-async-std`  | `async-std` |
| `rt-smol`       | `smol`      |

Enable exactly one. The crate emits a `compile_error!` if none or more than one
is selected, so misconfiguration fails fast at build time.

## License

Licensed under either of

- Apache License, Version 2.0
- MIT license

at your option.
