Go
Notes on the Go programming language — interfaces, concurrency, testing, and patterns that have held up in production.
A for-range over a channel that's never closed leaks the receiver. Why a fixed number of receives is safe, why a range isn't, and how to catch it with Go 1.27's leak profile.
How cmd/go's script tests led me to testscript, and how to use it for CLI tests that exercise argv, stdout, stderr, exit codes, and scratch files.
txtar is a tiny plain-text archive format Russ Cox introduced in 2018 for multi-file test fixtures. The Go Playground, cmd/go's script tests, gopls's marker tests, and rsc.io/rf all reach for it.
The default slog API is loose enough that a careless line ships broken JSON to production. Pin it down with Attr constructors, LogAttrs, a context-borne logger, and sloglint.
Four of the five steps in every unary RPC handler are wire plumbing. Pin the service function signature and they fit in one generic adapter per transport.
A Go closure holds a live reference to whatever it captures, not a snapshot. Real examples of where this trips people up, and how to keep it boring.
A quick tour of Go struct tags: how different libraries use them, how you read them at runtime with reflection, and how other tools read them at build time instead.
Translating errors at layer boundaries so storage details don't leak into the handler or, worse, into client responses.
How to test unary gRPC services in Go - handler logic, interceptors, deadlines, metadata propagation, and rich error details - all in-memory with bufconn.
Decoupling business logic from storage in Go, adding transaction support without leaking SQL details, and coordinating atomic writes across multiple repositories using a unit of work.
How to wrap a generated gRPC client behind a clean Go API so users never have to touch protobuf types or connection management directly.
Exploring the tradeoffs between wrapping errors at every return site versus wrapping only at boundaries, with no definitive answer - just honest tradeoffs for the kind of software I write.
Why your mutex wrapper should accept a closure for mutation instead of a plain value, with examples from the standard library and Tailscale.
How Go 1.20's WithCancelCause and Go 1.21's WithTimeoutCause let you attach a reason to context cancellation, plus a gotcha with manual cancel and the stdlib pattern that covers every path.
How Python and Kotlin provide structured concurrency out of the box while Go achieves the same patterns explicitly using errgroup, WaitGroup, and context.