Ringslice is a type-safe generic ring buffer backed by a Go slice, designed for production use cases like sliding window and streaming workloads.
Most ring buffer implementations focus on storage. Ringslice adds lifecycle hooks
so you can react to what happens as data moves through the buffer.
It also features iter.Seq iteration for idiomatic Go range support.
For pointer-based circular lists and round-robin traversal, see Go’s standard container/ring.
const maxRingCapacity = 128
ring := ringslice.New[string](maxRingCapacity)
ring.Add("generic")
ring.Add("ring")
ring.Add("buffer")
// prints: "generic", "ring", "buffer"
for v := range ring.All() {
fmt.Println(v)
}
// prints: "buffer", "ring", "generic"
for v := range ring.AllDesc() {
fmt.Println(v)
}Ringslice provides callback hooks that let you react to internal lifecycle events.
Called before the value is added to the ring. The value will be rejected if false is returned.
This is particularly useful if you want to exclude values with certain characteristics.
// reject empty strings
ring.OnBeforeAdd(func(value string) bool {
return len(value) > 0
})Called each time the write index wraps around the ring. Useful for logging, flushing, or instrumentation.
ring.OnRotate(func(values []string) {
fmt.Println("lap complete 🏁")
})Called by Flush() before the buffer is cleared. Useful for draining the buffer, persisting elements, or instrumentation.
ring.OnFlush(func(values []string) {
for _, v := range values {
db.Insert(v)
}
})Note: Clear() discards all elements without invoking this callback.
Ringslice uses a read-write lock to allow multiple concurrent readers while serializing writers. This ensures thread-safety while maintaining high throughput for read-heavy workloads.