Day 22 — Generics (Go 1.18+)
Learning Objectives
- Write generic data structures used across your entire security proxy
- Build a
RingBuffer[T]used for metrics, log entries, and connection histories - Understand constraints: when
comparablevsany
Key Concepts
// Generic function — works for any ordered numeric type
func Min[T int | float64 | int64](a, b T) T {
if a < b { return a }
return b
}
// Generic type — RingBuffer[T] works for any T
type RingBuffer[T any] struct {
items []T
head int
count int
size int
}
// Type constraints
type Number interface {
int | int8 | int16 | int32 | int64 | float32 | float64
}
// comparable — for map keys and equality checks
func Contains[T comparable](slice []T, item T) bool {
for _, s := range slice {
if s == item { return true }
}
return false
}
Exercise: Generic RingBuffer for the Security Stack
package main
import "fmt"
// RingBuffer is a fixed-size circular buffer — O(1) add, O(n) scan
// Used for: last-N request log, latency histogram, connection history
type RingBuffer[T any] struct {
items []T
head int
count int
size int
}
func NewRingBuffer[T any](size int) *RingBuffer[T] {
return &RingBuffer[T]{items: make([]T, size), size: size}
}
func (rb *RingBuffer[T]) Add(item T) {
rb.items[rb.head] = item
rb.head = (rb.head + 1) % rb.size
if rb.count < rb.size {
rb.count++
}
}
func (rb *RingBuffer[T]) All() []T {
result := make([]T, rb.count)
for i := 0; i < rb.count; i++ {
result[i] = rb.items[(rb.head-rb.count+i+rb.size)%rb.size]
}
return result
}
func (rb *RingBuffer[T]) Len() int { return rb.count }
// Result[T] — type-safe wrapper for (value, error)
type Result[T any] struct {
Value T
Err error
}
func OK[T any](v T) Result[T] { return Result[T]{Value: v} }
func Err[T any](e error) Result[T] { return Result[T]{Err: e} }
func (r Result[T]) Unwrap() (T, error) { return r.Value, r.Err }
func main() {
// Latency ring buffer (last 1000 request latencies for p99 calc)
latencies := NewRingBuffer[int64](1000)
for _, ms := range []int64{5, 12, 8, 45, 3, 100, 7, 23} {
latencies.Add(ms)
}
fmt.Printf("Last %d latencies: %v\n", latencies.Len(), latencies.All())
// Log entry ring buffer
type LogEntry struct{ IP, Path string; Status int }
logs := NewRingBuffer[LogEntry](100)
logs.Add(LogEntry{"10.0.0.1", "/api/users", 200})
logs.Add(LogEntry{"10.0.0.2", "/api/admin", 403})
for _, e := range logs.All() {
fmt.Printf("%s %s %d\n", e.IP, e.Path, e.Status)
}
// Result type for auth operations
authResult := OK("user:42")
if user, err := authResult.Unwrap(); err == nil {
fmt.Println("authenticated:", user)
}
}
Real-World Context
The RingBuffer[int64] stores the last 1000 request latencies. A simple sort of .All() gives you p50, p95, p99. This is your home-grown metrics system — no Prometheus library needed. You'll use it in the Week 4 mini project and connect it to the observability layer in Week 9.