goish by Cogentica AI

A Rust runtime for Go people.

A Go-style standard library and runtime, implemented in no_std Rust on top of raw Linux syscalls. No glibc, no std, no Tokio. Goish ships its own _start, page allocator, size-class heap, M:N scheduler, channels, select!, sync primitives, async preemption, and ~80 ports of Go standard-library and golang.org/x packages. Because it's no_std, Rust compiles the whole thing, so you can ship a static binary just like Go.

The Goish mascot: Ferris the Rust crab wearing a Go gopher hoodie
examples/spawn_million.rs rust
use goish::{go, KB};
use goish::sync::WaitGroup;

#[goish::main]
fn main() {
    let wg = &WaitGroup::new();
    wg.Add(1_000_000);

    for i in 0..1_000_000 {
        // Explicit 2 KiB stack, sub-page allocated from the chunked
        // stackpool - the opt-in for extreme spawn density. Everyday
        // code just writes go!(move || ...) and never sizes a stack.
        go!(stack(2 * KB), move || {
            do_work(i);
            wg.Done();
        });
    }

    wg.Wait();
}
ts vmsize_kb vmrss_kb vmpeak_kb vmhwm_kb threads
0s 1105148 44800 1108444 49024 13 ← baseline
1s 2116924 1271680 2126588 1276288 13 ← spawning
2s 3069660 2406528 3069660 2406528 13 ← 1M parked
30s 3069660 2406528 3069660 2406528 13 ← steady-state
32s 3015964 2348044 3069660 2406528 13 ← released

A million real goroutines on 13 OS threads: ~2 GiB virtual, ~2.4 KiB peak RSS per goroutine at sub-page density. 16-core x86_64, kernel 6.8.

1,000,000
goroutines parked at once
~80
Go stdlib & x/ packages ported
406
examples in the e2e suite
0
glibc, std, Tokio, GC
1
static binary, no ld.so

reads like Go

Multi-return, if err != nil, channels, select!

Public API surfaces use Go's lowercase types: string, slice<T>, map<K, V>, chan<T>, int. Vec<u8> and &str don't appear in public signatures. These excerpts are verbatim from goginx, the nginx clone in the examples tree.

error handling, the Go way rust
fn get(url: string) -> (int, string, string) {
    let (mut resp, err) = http::Get(url.clone());
    if err != nil {
        return (-1, fmt::Sprintf!("get %s: %v", url, err), string(""));
    }
    let (body, _) = io::ReadAll(&mut resp.Body);
    let _ = io::Closer::Close(&mut resp.Body);
    return (
        resp.StatusCode,
        string(body),
        resp.Header.Get("Content-Type"),
    );
}
select with a timeout arm rust
select! {
    let _ = done.Recv() => {},
    let _ = (time::After(time::Second * 10)).Recv() => {
        fail("drain: timed out waiting for done");
    },
}

built like a runtime

Go 1.25's runtime idioms, down to raw syscalls

The G/M/P scheduler is ported verbatim from Go 1.25's runtime/proc.go: lock-free per-P run queues, coprime-permuted work stealing, SIGURG async preemption, sysmon, and a per-P epoll netpoller. Underneath sit Go's radix-tree page allocator and 67 size classes, reimplemented over raw mmap.

what's inside

What you get

goroutines & scheduler

M:N stackful goroutines with async preemption. Bare go!() costs ~one physical page; overflow hits a guard page with a spawn-site diagnostic. go!(stack(N), ...) opts into sub-page density.

channels, select!, sync

Unbuffered, buffered, nil, close. Intrusive sudog wait queues mean zero allocator round-trips on park/unpark. sync::{Mutex, RWMutex, WaitGroup, Once}, context, and a sysmon-driven timer heap.

net/http, production-hardened

HTTP/1.1 with keep-alive, Go 1.22 ServeMux wildcards, middleware, streaming, reverse proxy, and graceful Shutdown(ctx). Blocking reads park the goroutine, not the thread. HTTPS via goish's own TLS 1.3 stack.

crypto, ported ground-up

aes, sha256, ecdh, ed25519, rsa, x509, chacha20poly1305 and more. These back the client TLS 1.2/1.3 and server TLS 1.3 handshakes, plus an SSH-2.0 client.

memory without a GC

Go's page allocator and size-class heap, minus the collector: demand-paged MAP_NORESERVE arenas, per-P caches, and a lock-free hot path. Rust ownership does the reclaiming.

~80 stdlib packages

fmt, io, bufio, encoding/json (v2), regexp, compress, archive/tar, testing, log/slog, math/big, reflect and more, all Go 1.25-faithful, plus golang.org/x ports.

comparison

Where goish sits

goish Go Pure Rust async
Concurrency M:N, stackful Gs M:N, growable stacks stackless futures
Stack per G 1 MiB reserved, lazy-commit (2 KiB opt-in) 2 KiB growable one Future per task
Preemption SIGURG (async) SIGURG (async) cooperative .await
1M goroutines yes, in 2 GiB virtual yes needs runtime tuning
Standalone binary yes, no glibc, no ld.so static linkable needs std
GC none (manual mheap) concurrent mark+sweep none

Goish is not a clone of Go. It ports the runtime idioms into a Rust ownership model. Go's morestack is impossible here, so goish grows the other way: virtual reservations the kernel commits on touch. No GC either way. It is single-target (x86_64-unknown-linux-gnu) and under active development. The e2e suite spans 406 examples.

build & run

One target. One binary.

Rust 1.90+, Linux x86_64. Binaries are statically linked: cat /proc/<pid>/maps shows only the binary itself plus mmap'd arenas.

shell bash
# the library
cargo build --target x86_64-unknown-linux-gnu --release

# the million-goroutine demo
cargo build --target x86_64-unknown-linux-gnu --release --example spawn_million
./examples/spawn_million.sh

# goginx: the nginx clone written in goish
cargo build --target x86_64-unknown-linux-gnu --release --example goginx