← GophersCONTENT HISTORYWHAT CHANGED · RULE-BASED ANALYSIS
Update to Gophers
Snapshot Sep 30, 2026 · 23:14 UTC · version 0.1.0
Collection source: not recorded for this historical snapshot.
First saved snapshot
No earlier snapshot is available to establish a change.
Compare saved observations
Download comparison JSONFull technical diff · 0 changed fields
Full snapshot data
{
"description": "Use when writing conditionals, loops, switches, type switches, or blank-identifier patterns in Go. Covers if-with-initialization, guard clauses, early returns, the unified for loop, range over slices/maps/strings/channels, parallel assignment, labeled break, and `_` for discards and side-effect imports. Apply proactively to any new if/for/switch, even when the user does not mention scoping or shadowing. Does not cover error-flow specifics (see go-error-handling).",
"included_files": [
{
"relative_path": "agents/openai.yaml",
"size_in_bytes": 224
},
{
"relative_path": "references/blank-identifier.md",
"size_in_bytes": 1647
},
{
"relative_path": "references/switch-patterns.md",
"size_in_bytes": 1954
}
],
"name": "go-control-flow",
"skill_md_contents": "---\nname: go-control-flow\ndescription: \"Use when writing conditionals, loops, switches, type switches, or blank-identifier patterns in Go. Covers if-with-initialization, guard clauses, early returns, the unified for loop, range over slices/maps/strings/channels, parallel assignment, labeled break, and `_` for discards and side-effect imports. Apply proactively to any new if/for/switch, even when the user does not mention scoping or shadowing. Does not cover error-flow specifics (see go-error-handling).\"\nlicense: MIT\ncompatibility: \"Designed for Claude Code or similar AI coding agents. Plain Go (any supported version).\"\nallowed-tools: Read Edit Write Glob Grep Bash(go:*) Bash(golangci-lint:*)\n---\n\n# Go Control Flow\n\nGo gives you `if`, `for`, and `switch` — and one looping construct that covers them all. The idioms are small but strict: scope variables tightly, return early, keep the happy path unindented.\n\n## Core Rules\n\n1. **Scope variables with if-init when they live only for the check.** `if x, err := f(); err != nil { ... }`.\n2. **Guard clauses over nested `else`.** When the `if` body returns/breaks/continues, drop the `else`.\n3. **`:=` redeclares only in the same scope.** In an inner scope it shadows — a frequent bug source.\n4. **One `for`, three forms.** Condition-only (while), three-clause (C-style), and infinite (`for {}`).\n5. **`range` over string yields runes**, over map yields **non-deterministic order**, over channel **drains until closed**.\n6. **`break` inside `switch` only breaks the switch.** Use a labeled `break` to exit the enclosing `for`.\n7. **The blank identifier `_` discards, but never errors.** Silent error dropping is a bug.\n\n## Decision: var vs := vs = at a glance\n\n| Situation | Use |\n|---|---|\n| New variable, scoped to the check | `if v, err := f(); err != nil` |\n| Reusing an outer variable | plain `=` (avoid `:=` to prevent shadowing) |\n| At least one new + reuse of outer | `:=` is fine (same scope only) |\n| Wanted zero value | `var x T` |\n\n## If with Initialization\n\n```go\nif err := file.Chmod(0664); err != nil {\n return err\n}\n```\n\nIf `err` is needed past the `if`, declare it separately:\n\n```go\nx, err := f()\nif err != nil {\n return err\n}\n// use x freely\n```\n\n## Guard Clauses\n\n```go\nf, err := os.Open(name)\nif err != nil {\n return err\n}\nd, err := f.Stat()\nif err != nil {\n f.Close()\n return err\n}\ncodeUsing(f, d)\n```\n\nNever bury the success path inside `else`.\n\n## The Shadowing Trap\n\n```go\n// Bug: inner ctx never escapes the if block\nif *shortenDeadlines {\n ctx, cancel := context.WithTimeout(ctx, 3*time.Second)\n defer cancel()\n}\n\n// Fix: assign with =, declaring cancel separately\nvar cancel func()\nctx, cancel = context.WithTimeout(ctx, 3*time.Second)\ndefer cancel()\n```\n\n> Read [references/blank-identifier.md](references/blank-identifier.md) for `_` use cases (interface checks, side-effect imports, multi-return discards).\n\n## For Loops\n\n```go\n// Condition-only (Go's \"while\")\nfor x > 0 { x = process(x) }\n\n// Infinite\nfor {\n if done() { break }\n}\n\n// Three-clause\nfor i := 0; i < n; i++ { ... }\n```\n\n### Range\n\n```go\nfor i, v := range slice { ... } // index + value\nfor k, v := range myMap { ... } // non-deterministic order\nfor i, r := range \"héllo\" { ... } // i is byte offset; r is rune\nfor v := range ch { ... } // drains until closed\n```\n\n### Parallel Assignment\n\nGo has no comma operator. Use parallel assignment instead:\n\n```go\nfor i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 {\n a[i], a[j] = a[j], a[i]\n}\n```\n\n`++` and `--` are statements, not expressions — they cannot appear inside a parallel assignment.\n\n## Switch and Labeled Break\n\n```go\nLoop:\n for _, v := range items {\n switch v.Type {\n case \"done\":\n break Loop // breaks the for, not just the switch\n }\n }\n```\n\n> Read [references/switch-patterns.md](references/switch-patterns.md) for expression-less switches, comma cases, fallthrough, and type switches.\n\n## Anti-Patterns\n\n| Anti-pattern | Why it hurts | Do this instead |\n|---|---|---|\n| `} else { return ... }` after a returning `if` | Pointless nesting | Drop `else`; let the happy path stay flat |\n| `if _, err := f(); err == nil { ... }` then use the value | Value is out of scope | Move the `:=` outside the `if` |\n| `:=` in inner scope reassigning outer var | Silently shadows | Use `=` (declare the new locals separately) |\n| Iterating a map and relying on order | Order is randomized | Sort keys explicitly |\n| `break` inside `switch` expecting to exit `for` | Only exits switch | Use labeled `break Label` |\n| `_ = doSomething()` to silence an error | Real failures vanish | Handle it or document why |\n\n## Verification Checklist\n\n- [ ] No `else` branch after an `if` that returns/breaks/continues\n- [ ] `:=` in inner scopes does not shadow important outer variables\n- [ ] Loops use the simplest of the three forms that fits\n- [ ] `range` over strings treats the index as a byte offset, not a rune index\n- [ ] Map iteration does not assume an order\n- [ ] Errors are never silently discarded with `_`\n- [ ] Labeled `break` is used when a switch needs to exit a surrounding loop\n\n## References\n\n- [references/switch-patterns.md](references/switch-patterns.md) — expression-less, comma cases, fallthrough, type switches\n- [references/blank-identifier.md](references/blank-identifier.md) — `_` for multi-return, interface compliance, side-effect imports\n"
}SHA-256 of public snapshot: f04ece797fbed0fb6f34e3bdd39541b266bd8d2124d48a04f1f049bd1c5021d4