The Concurrency Revolution in C++

For years, C++ developers wrestling with asynchronous I/O faced a harsh choice: endure callback spaghetti or pay the overhead of thread synchronization. The introduction of native coroutines in C++20 promised liberation—but required elegant libraries to unlock its potential. Enter coco, a stackless, single-threaded header-only library that brings Golang's CSP philosophy to modern C++.

The Go Inspiration

coco's design follows Golang's core principle: "Don’t communicate through shared memory, share memory through communication." As creator Luajit notes, fragmented callbacks in C++ I/O programming often create unmaintainable code. coco solves this by implementing:

  • Channels for goroutine-style communication
  • Waitgroups for task coordination
  • Async/await syntax via native C++20 coroutines
  • Single-threaded scheduler avoiding lock contention

Syntax That Feels Like Home

Defining coroutines mirrors familiar patterns:

co_t fetch_data(int id) {
    std::cout << "Task " << id << " started
";
    co_yield resched; // Explicit yield point
    auto result = co_await async_io_operation();
    co_return result;
}

Channels enable elegant producer/consumer workflows:

chan_t<std::string> buffer(5); // Buffered channel

co_t producer() {
    while (auto item = co_await produce_item()) {
        co_await buffer.write(*item); // Blocks when full
    }
    buffer.close();
}

The I/O Revolution

The power shines in I/O-bound applications. Contrast traditional io_uring callback hell:

void handle_completion(struct io_uring_cqe* cqe) {
    switch (req->event_type) { /* 40 lines of state tracking */ }
}

With coco's linear flow:

co_t handle_connection(int socket) {
    auto request = co_await async_read(socket);
    auto response = process(request);
    co_await async_write(socket, response);
    close(socket);
}

Under the Hood: Performance Meets Safety

coco leverages C++20's stackless coroutines for efficiency:

  • Zero heap allocations per operation (coroutine frame allocated once)
  • RAII compliance with deterministic destruction
  • Exception propagation through coroutine joins

But critical caveats exist:

co_t danger() {
    std::vector data{1,2,3};
    int* ptr = &data[0]; // Pointer to local
    co_yield resched;
    *ptr = 42; // UNDEFINED BEHAVIOR: Frame may relocate!
}

RAII objects like std::vector remain safe across suspensions, but raw pointers to stack variables become invalid when the coroutine frame moves.

When to Use coco

This paradigm excels in:
- High-throughput network services (HTTP servers, proxies)
- Async data pipelines
- Protocol state machines
- Embedded systems avoiding thread overhead

As Luajit emphasizes, coco provides "clean, readable code" without sacrificing performance—benchmarks show near-identical throughput to native io_uring callbacks.

The New Concurrency Primitive

coco represents more than utility—it's a philosophical shift toward communicational concurrency in C++. By blending Go's elegance with C++'s performance, it demonstrates how native language features can transform development paradigms. For teams building modern async systems, this header-only library offers an escape from callback purgatory.

Source: coco GitHub repository and project announcement