Day 4 — Slices & Maps
Learning Objectives
- Understand slice internals to avoid silent bugs and memory leaks
- Use maps correctly in concurrent code (maps are not thread-safe)
- Build an in-memory request log — the foundation of your WAF audit trail
Key Concepts
// Slice internals: header = (pointer, length, capacity)
s := make([]int, 0, 10) // len=0, cap=10 — no allocation on append until cap exceeded
// append may reallocate — never assume the underlying array is the same
a := []int{1, 2, 3}
b := append(a, 4) // b may share memory with a if cap allows, or not
// Slice of slice — shares memory
original := []byte("hello world")
sub := original[6:11] // "world" — same backing array, not a copy
sub[0] = 'W' // modifies original too!
// Map operations
m := make(map[string]int)
m["requests"] = 42
count, ok := m["requests"] // ok=true when key exists
if !ok { /* key not present */ }
delete(m, "requests")
// Iterating — order is random (by design, to prevent relying on it)
for key, value := range m {
fmt.Printf("%s: %d\n", key, value)
}
Exercise: Ring Buffer Request Log
package main
import (
"fmt"
"net"
"time"
)
type Request struct {
IP net.IP
Path string
Status int
Latency time.Duration
At time.Time
}
// RingBuffer holds the last N requests without unbounded growth.
// Critical for WAF and rate-limiting — you don't want to buffer all requests forever.
type RingBuffer struct {
entries []Request
head int
count int
size int
}
func NewRingBuffer(size int) *RingBuffer {
return &RingBuffer{entries: make([]Request, size), size: size}
}
func (rb *RingBuffer) Add(r Request) {
rb.entries[rb.head] = r
rb.head = (rb.head + 1) % rb.size
if rb.count < rb.size {
rb.count++
}
}
func (rb *RingBuffer) All() []Request {
result := make([]Request, rb.count)
for i := 0; i < rb.count; i++ {
idx := (rb.head - rb.count + i + rb.size) % rb.size
result[i] = rb.entries[idx]
}
return result
}
// IPIndex provides O(1) lookup of requests by source IP
type RequestLog struct {
ring *RingBuffer
byIP map[string][]int // IP -> slice of ring indices (approximate)
}
func NewRequestLog(size int) *RequestLog {
return &RequestLog{
ring: NewRingBuffer(size),
byIP: make(map[string][]int),
}
}
func (rl *RequestLog) Add(r Request) {
rl.ring.Add(r)
}
func main() {
log := NewRequestLog(1000)
// Simulate requests
for i := 0; i < 5; i++ {
log.Add(Request{
IP: net.ParseIP("192.168.1.1"),
Path: fmt.Sprintf("/api/resource/%d", i),
Status: 200,
Latency: time.Duration(i+1) * time.Millisecond,
At: time.Now(),
})
}
for _, r := range log.ring.All() {
fmt.Printf("[%s] %s %s %d %v\n", r.At.Format(time.RFC3339), r.IP, r.Path, r.Status, r.Latency)
}
}
Real-World Context
WAFs, IDS systems, and rate limiters all use ring buffers. An unbounded slice would OOM a production server. The map index enables O(1) "show all requests from IP X" — critical for incident response.