Show HN: A pure C89 implementation of Go channels, with blocking selects

A new open-source library that implements Go-style channels and blocking selects in C89 has sparked debate over whether targeting such an old C standard is a strength for portability or an unnecessary constraint that harms API quality. Commenters dig into design details such as naming conventions, use of `bool`, select semantics, allocator hooks, and concurrency edge cases like time-of-check/time-of-use bugs when channels close, while comparing the project to alternatives like libmill, libdill, and ZeroMQ. Alongside technical critique, several contributors reflect on tone and etiquette in public code reviews, highlighting the tension between rigorous feedback and maintaining a welcoming environment for volunteer maintainers.

Overall reception

  • Many commenters find the idea of Go-style channels in C neat and useful, especially for constrained or embedded environments.
  • Several express appreciation for the author’s effort and portability focus, even if they won’t personally use C89.

C89 vs newer C standards

  • One camp argues C89 is outdated: C11/C17 (and partly C99) are widely available, and C89-only constraints degrade API clarity (e.g., lack of bool) and code quality.
  • Others defend C89 as a practical portability target for old compilers, embedded toolchains, and projects built with TinyCC or other limited environments.
  • Some point out the library isn’t strictly C89 anyway (mixed declarations/statements, POSIX threads, certain extensions), arguing the “pure C89” label is misleading; the author later clarifies they mean “compiles with -std=c89” rather than strict formal purity.

API and design feedback

  • Suggestions include:
    • Use a bool-like type for status-style returns when possible.
    • Prefer _create/_destroy naming over _dispose.
    • Avoid _t suffix because POSIX reserves it (others say this is effectively a non-issue).
    • Allow custom allocators, logging hooks, and debug flags.
    • Expose a file descriptor so channels can integrate with existing event loops (select/epoll/kqueue/io_uring).
  • Some highlight that these are “header-level” design decisions that are hard to change later without breaking ABI.

Concurrency semantics & footguns

  • Concern about while (!closed(chan)) { … }: classic time-of-check/time-of-use bug if the channel closes between the check and operation.
  • Comparisons to Go: its receive operation atomically returns both value and “open/closed” status; this pattern is recommended instead of a separate closed() probe.
  • Questions raised about what happens to buffered messages on close and behavior when sending to a closed channel; commenters want this clearly documented.

Comparisons to other libraries and models

  • Related projects mentioned: libmill/libdill (CSP with coroutines and IO multiplexing), ZeroMQ inproc sockets, Plan 9 libthread, CSP and actors.
  • Clarification that this library uses OS threads, not user-level coroutines, so it can’t be safely mixed with coroutine schedulers that expect non-blocking primitives.

Community & tone

  • There is substantial meta-discussion about review tone: some feel early critiques were overly harsh; others stress that the technical feedback itself is valuable if delivered with more care.