IN-PROCESS · FIRE-AND-FORGET · v1.2.0

An event bus that moves at memory.

A minimal in-process event bus for Go — typed events, cancelable listeners, one-shot handlers. Built on the zero-allocation go-pubsub engine. The emit hot path allocates nothing for a known event; a brand-new event value costs one topic slot.

Go Reference Go Report Card coverage 96.8% MIT license

Why go-bus

Small surface. Sharp edges.

227ns per Emit, 1000 listeners
0allocs on the publish hot path
96.8% statement coverage
4types Bus · Event · Listener · ListenFunc

When

Right tool, narrow job.

A great fit

  • Domain & application events fanned out in one process
  • Decoupling producers from consumers
  • One-shot hooks — “run once on first X”
  • Lightweight fan-out to N subscribers

Not a good fit

  • Cross-process / distributed pub-sub — use NATS, Kafka
  • Durable queues or at-least-once delivery
  • Strict reliability — full channels drop silently
  • Backpressure / acknowledgement paths

go-bus is fire-and-forget by design: Emit never blocks, there is no persistence, no redelivery. If a subscriber falls behind, messages are dropped.

Start

Five steps to first event.

main.gocopy
// 1 · create a bus (inject your own logger if you like)
eventBus, _ := bus.NewBus(bus.WithLogger(logger))

// 2 · events are typed constants
const ( ConfigReloaded = iota + 1; ThresholdBreached )
reload := bus.NewEvent(ConfigReloaded)

// 3 · register a listener
listener := bus.NewListener(func(msg any) { invalidate(msg) })
_ = eventBus.On(reload, listener)

// 4 · emit — non-blocking, zero-alloc
_ = eventBus.Emit(reload, "new.yaml")

// 5 · stop listening whenever
listener.Cancel()

Or grab it: go get github.com/F2077/go-bus  ·  requires Go 1.25+. A runnable copy lives at cmd/quickstart.

Surface

The whole API, one screen.

bus.NewBus(opts...) → (*Bus, error)

Construct a bus. WithLogger injects an slog.Logger.

bus.NewEvent(int) → Event

Wrap an integer constant into a typed event key.

(*Bus).On(Event, *Listener) → error

Subscribe a listener; one goroutine drains it until canceled.

(*Bus).Emit(Event, any) → error

Dispatch to every listener on that event. Never blocks.

bus.NewListener(ListenFunc, opts...) → *Listener

WithOnetime(true) auto-removes after the first trigger.

(*Listener).Cancel()

Stop the listener’s goroutine via its context.

Numbers

Measured, not promised.

BenchmarkSetupns/opB/opallocs
BenchmarkEventBus1000 listeners, parallel22700
Fanout / subs=11 listener22100
Fanout / subs=1010 listeners1,37300
Fanout / subs=100100 listeners4,64400
Fanout / subs=10001000 listeners36,448100
OneTimeListenerOn + Emit + remove6,5954,14220

go test -bench=. -benchmem -benchtime=1s, Go 1.25, Intel Core Ultra 5 125H. Indicative — treat as relative. The emit path stays zero-allocation up to 1000 subscribers.