Go has no unbounded channels. Why not, and how would you build one?
A channel's capacity is fixed at make time. The Go team has declined to add unbounded channels (the proposal is golang/go#20352) because a bounded buffer gives you backpressure: a slow consumer eventually blocks the producer. An unbounded queue hides that until memory runs out. If you really need one, for example to keep an event loop from ever blocking, build it from a goroutine, a slice, and the nil-channel trick:
func Unbounded[T any](ctx context.Context, in <-chan T) <-chan T {
out := make(chan T)
go func() {
defer close(out)
var queue []T
for in != nil || len(queue) > 0 {
var sendCh chan T // nil: send case disabled
var next T
if len(queue) > 0 {
sendCh, next = out, queue[0]
}
select {
case v, ok := <-in:
if !ok {
in = nil // stop receiving, flush the queue
continue
}
queue = append(queue, v)
case sendCh <- next:
var zero T
queue[0] = zero // let the GC reclaim the value
queue = queue[1:]
case <-ctx.Done():
return
}
}
}()
return out
}
Points to mention: next is computed before the select because send values are evaluated even for disabled cases. Zeroing queue[0] avoids keeping pointers alive in the slice's backing array. The ctx case keeps the goroutine from leaking if the consumer walks away. Order is FIFO. Memory growth is unbounded by design, so expose a length metric or put a hard cap on it and drop items.
More on Channels & select
- Q217What does this print? (
breakinsideselectinsidefor) - Q218What happens to goroutines that are already blocked on a channel when it is closed?
- Q220What exactly gets copied when you send on a channel, and what limits apply to element types and buffer sizes?