Go control flow
26 production-grade Go skills for Claude Code, Gemini CLI, and opencode.
npx -y skills add muratmirgun/gophers --skill go-control-flowAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 8 stars8 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
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).
The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.
SKILL.md
5.5 KB, as published. Nobody here has run it
Go Control Flow
Go 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.
Core Rules
- Scope variables with if-init when they live only for the check.
if x, err := f(); err != nil { ... }. - Guard clauses over nested
else. When theifbody returns/breaks/continues, drop theelse. :=redeclares only in the same scope. In an inner scope it shadows — a frequent bug source.- One
for, three forms. Condition-only (while), three-clause (C-style), and infinite (for {}). rangeover string yields runes, over map yields non-deterministic order, over channel drains until closed.breakinsideswitchonly breaks the switch. Use a labeledbreakto exit the enclosingfor.- The blank identifier
_discards, but never errors. Silent error dropping is a bug.
Decision: var vs := vs = at a glance
| Situation | Use |
|---|---|
| New variable, scoped to the check | if v, err := f(); err != nil |
| Reusing an outer variable | plain = (avoid := to prevent shadowing) |
| At least one new + reuse of outer | := is fine (same scope only) |
| Wanted zero value | var x T |
If with Initialization
if err := file.Chmod(0664); err != nil {
return err
}
If err is needed past the if, declare it separately:
x, err := f()
if err != nil {
return err
}
// use x freely
Guard Clauses
f, err := os.Open(name)
if err != nil {
return err
}
d, err := f.Stat()
if err != nil {
f.Close()
return err
}
codeUsing(f, d)
Never bury the success path inside else.
The Shadowing Trap
// Bug: inner ctx never escapes the if block
if *shortenDeadlines {
ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
defer cancel()
}
// Fix: assign with =, declaring cancel separately
var cancel func()
ctx, cancel = context.WithTimeout(ctx, 3*time.Second)
defer cancel()
Read references/blank-identifier.md for
_use cases (interface checks, side-effect imports, multi-return discards).
For Loops
// Condition-only (Go's "while")
for x > 0 { x = process(x) }
// Infinite
for {
if done() { break }
}
// Three-clause
for i := 0; i < n; i++ { ... }
Range
for i, v := range slice { ... } // index + value
for k, v := range myMap { ... } // non-deterministic order
for i, r := range "héllo" { ... } // i is byte offset; r is rune
for v := range ch { ... } // drains until closed
Parallel Assignment
Go has no comma operator. Use parallel assignment instead:
for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 {
a[i], a[j] = a[j], a[i]
}
++ and -- are statements, not expressions — they cannot appear inside a parallel assignment.
Switch and Labeled Break
Loop:
for _, v := range items {
switch v.Type {
case "done":
break Loop // breaks the for, not just the switch
}
}
Read references/switch-patterns.md for expression-less switches, comma cases, fallthrough, and type switches.
Anti-Patterns
| Anti-pattern | Why it hurts | Do this instead |
|---|---|---|
} else { return ... } after a returning if | Pointless nesting | Drop else; let the happy path stay flat |
if _, err := f(); err == nil { ... } then use the value | Value is out of scope | Move the := outside the if |
:= in inner scope reassigning outer var | Silently shadows | Use = (declare the new locals separately) |
| Iterating a map and relying on order | Order is randomized | Sort keys explicitly |
break inside switch expecting to exit for | Only exits switch | Use labeled break Label |
_ = doSomething() to silence an error | Real failures vanish | Handle it or document why |
Verification Checklist
- No
elsebranch after anifthat returns/breaks/continues -
:=in inner scopes does not shadow important outer variables - Loops use the simplest of the three forms that fits
-
rangeover strings treats the index as a byte offset, not a rune index - Map iteration does not assume an order
- Errors are never silently discarded with
_ - Labeled
breakis used when a switch needs to exit a surrounding loop
References
- references/switch-patterns.md — expression-less, comma cases, fallthrough, type switches
- references/blank-identifier.md —
_for multi-return, interface compliance, side-effect imports