go
Use when writing Go — command-line tools, small HTTP services with the standard library or Echo, WebAssembly builds with TinyGo, table-driven tests and single static binaries. Also use when choosing b
SKILL.mdskills/languages/go/SKILL.md
GitHubGo
Default: current Go release, modules, standard library first (flag, net/http, encoding/json, log/slog), go test, go vet, gofmt.
Setup
go mod init example.com/tool; keepgo.mod'sgoline at the version you test with.- Layout:
cmd/<tool>/main.gofor binaries,internal/for code no one else should import. Do not createpkg/by habit. - A user-level install (unpack the official tarball into a home directory) is enough; add it to
PATHonly for the shell that needs it.
Command-line tools
flaghandles most tools; switch to a library only for nested subcommands.- Let configuration come from a flag with an environment-variable default, e.g.
-filedefaulting to$APP_DATA; document both in-hwith an example. - Print an actionable message on empty input ("no records yet — add one with ...") rather than an empty table.
- Exit codes:
os.Exit(1)for failures,2for usage errors.
HTTP services
- Start with
net/httpand the method-awareServeMuxpatterns (GET /items/{id}); add a framework only when middleware needs grow. - Always set server timeouts (
ReadHeaderTimeout,ReadTimeout,WriteTimeout); the zero value means none. - Pass
context.Contextfrom the request into every downstream call.
WebAssembly
- Standard Go WASM includes the runtime and is several MB. TinyGo produces binaries in the hundreds of KB for compute-only code; check that the packages you use are supported by TinyGo before choosing it.
- Expose functions with
syscall/js(or//exportin TinyGo) and keep the JS/WASM boundary coarse: pass a whole problem, not one number per call.
Testing
- Table-driven tests with
t.Run(name, ...); include argument-count and empty-input cases. go test -race ./...for anything with goroutines.- Use
t.TempDir()andt.Setenv(); they clean up automatically.
Verification loop
gofmt -l .prints nothing;go vet ./...is clean.go test ./...passes (with-racewhere relevant).go buildthe binary and run it with-hand with a real input.
Pitfalls
- Loop variable capture changed in Go 1.22 (each iteration has its own variable); old code and old toolchains differ — the
goline ingo.moddecides. - Nil interfaces: an interface holding a typed nil pointer is not
nil. - Maps are not safe for concurrent writes; guard with a mutex or use channels.
deferin loops runs at function end, not iteration end; wrap the body in a function.- JSON: unexported fields are ignored silently; use struct tags and
DisallowUnknownFieldsfor strict input. - Time:
time.Now()includes a monotonic reading; compare withSub, format with layouts based on2006-01-02.