返回 Skills
cxuu/golang-skills· Apache-2.0 内容可用

go-control-flow

Use when writing conditionals, loops, or switch statements in Go — including if with initialization, early returns, for loop forms, range, switch, type switches, and blank identifier patterns. Also use when writing a simple if/else or for loop, even if the user doesn't mention guard clauses or variable scoping. Does not cover error flow patterns (see go-error-handling).

安装

与 skills.sh 相同的 Command / Prompt 安装方式


name: go-control-flow description: Use when writing conditionals, loops, or switch statements in Go — including if with initialization, early returns, for loop forms, range, switch, type switches, and blank identifier patterns. Also use when writing a simple if/else or for loop, even if the user doesn't mention guard clauses or variable scoping. Does not cover error flow patterns (see go-error-handling).

Go Control Flow

Resource Routing

  • references/SWITCH-PATTERNS.md - Read when using switch statements, type switches, fallthrough, or labeled breaks.
  • references/BLANK-IDENTIFIER.md - Read when using _, blank imports, unused compile-time assertions, or intentional discards.

If with Initialization

if and switch accept an optional initialization statement. Use it to scope variables to the conditional block:

if err := file.Chmod(0664); err != nil {
    log.Print(err)
    return err
}

If you need the variable beyond a few lines after the if, declare it separately and use a standard if instead:

x, err := f()
if err != nil {
    return err
}
// lots of code that uses x

Indent Error Flow (Guard Clauses)

When an if body ends with break, continue, goto, or return, omit the unnecessary else. Keep the success path unindented:

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 normal flow inside an else when the if already returns.


Redeclaration and Reassignment

The := short declaration allows redeclaring variables in the same scope:

f, err := os.Open(name)  // declares f and err
d, err := f.Stat()       // declares d, reassigns err

A variable v may appear in a := declaration even if already declared, provided:

  1. The declaration is in the same scope as the existing v
  2. The value is assignable to v
  3. At least one other variable is newly created by the declaration

Variable Shadowing

Warning: If v is declared in an outer scope, := creates a new variable that shadows it — a common source of bugs:

// Bug: ctx inside the if block shadows the outer ctx
if *shortenDeadlines {
    ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
    defer cancel()
}
// ctx here is still the original — the shadowed ctx didn't escape

// Fix: use = instead of :=
var cancel func()
ctx, cancel = context.WithTimeout(ctx, 3*time.Second)

For Loops

Go's for is its only looping construct, unifying while, do-while, and C-style for:

// Condition-only (Go's "while")
for x > 0 {
    x = process(x)
}

// Infinite loop
for {
    if done() { break }
}

// C-style three-component
for i := 0; i < n; i++ { ... }

Range

range iterates over slices, maps, strings, and channels:

for i, v := range slice { ... }   // index + value
for k, v := range myMap { ... }   // key + value (non-deterministic order)
for i, r := range "héllo" { ... } // byte index + rune (not byte)
for v := range ch { ... }         // receives until channel closed

Key rules:

  • Range over strings yields runes, not bytes — i is the byte offset
  • Range over maps has non-deterministic order — don't rely on it
  • Use _ to discard the index or value: for _, v := range slice

Parallel Assignment

Go has no comma operator. Use parallel assignment for multiple loop variables:

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 in parallel assignment.


Switch: Labeled Break

break inside a switch within a for loop only breaks the switch. Use a labeled break to exit the enclosing loop:

Loop:
    for _, v := range items {
        switch v.Type {
        case "done":
            break Loop  // breaks the for loop
        }
    }

For type switches, see go-interfaces: Type Switch.


The Blank Identifier

Never discard errors carelessly — a nil dereference panic may follow.

Route compile-time interface assertions to go-interfaces.


Quick Reference

PatternGo Idiom
If initializationif err := f(); err != nil { }
Early returnOmit else when if body returns
Redeclaration:= reassigns if same scope + new var
Shadowing trap:= in inner scope creates new variable
Parallel assignmenti, j = i+1, j-1
Expression-less switchswitch { case cond: }
Comma casescase 'a', 'b', 'c':
No fallthroughDefault behavior (explicit fallthrough if needed)
Break from loop in switchbreak Label
Discard value_, err := f()
Side-effect importimport _ "pkg"
Interface checkRoute to go-interfaces

Related Skills

  • Error flow: See go-error-handling when structuring guard clauses, early returns, or error-first patterns
  • Type switches: See go-interfaces when using type switches, the comma-ok idiom, or interface satisfaction checks
  • Nesting reduction: See go-style-core when reducing nesting depth or resolving formatting questions
  • Variable scoping: See go-declarations when using if-init, := redeclaration, or reducing variable scope

附带文件

references/BLANK-IDENTIFIER.md
# Blank Identifier Patterns

The blank identifier `_` serves multiple roles in Go: discarding unwanted
values, importing packages for side effects, and verifying interface compliance
at compile time.

---

## Multiple Assignment

Use `_` to discard unwanted values from multi-value expressions:

```go
if _, err := os.Stat(path); os.IsNotExist(err) {
    fmt.Printf("%s does not exist\n", path)
}
```

### Never Discard Errors Carelessly

Silently discarding an error invites nil-pointer panics:

```go
// Bad: ignoring error will crash if path doesn't exist
fi, _ := os.Stat(path)
if fi.IsDir() { ... }  // nil pointer dereference
```

If you truly don't need the error, document why:

```go
_ = logger.Sync() // best-effort flush; error is non-actionable
```

---

## Import for Side Effect

Import a package solely for its `init()` side effects using the blank
identifier:

```go
import _ "net/http/pprof"  // registers HTTP handlers
import _ "image/png"       // registers PNG decoder
```

This is commonly used to register drivers, codecs, or debug handlers that
wire themselves into a registry during `init()`.

---

## Interface Satisfaction Checks

Blank identifiers are also used in compile-time interface assertions, but that
rule is owned by [go-interfaces](../../go-interfaces/SKILL.md). Use this
reference for blank-identifier mechanics; route assertion placement, need, and
validation decisions to the interface skill.

---

## Quick Reference

| Pattern | Syntax |
|---------|--------|
| Discard value | `_, err := f()` |
| Discard in if-init | `if _, err := f(); err != nil { }` |
| Side-effect import | `import _ "pkg"` |
| Interface check | Route to go-interfaces |
references/SWITCH-PATTERNS.md
# Switch Patterns

Detailed patterns for Go `switch` statements, including expression-less
switches, comma cases, break behavior, and labeled breaks.

---

## No Automatic Fallthrough

Go `switch` cases do **not** fall through by default (unlike C/Java). Each case
body implicitly breaks. Use `fallthrough` only when explicitly needed — it is
rare in idiomatic Go.

```go
switch n {
case 1:
    fmt.Println("one")
    // no fallthrough — next case is NOT executed
case 2:
    fmt.Println("two")
}
```

---

## Expression-less Switch

A `switch` with no expression switches on `true`. Use it for clean if-else-if
chains when comparing a single variable against multiple conditions:

```go
func unhex(c byte) byte {
    switch {
    case '0' <= c && c <= '9':
        return c - '0'
    case 'a' <= c && c <= 'f':
        return c - 'a' + 10
    case 'A' <= c && c <= 'F':
        return c - 'A' + 10
    }
    return 0
}
```

---

## Comma-Separated Cases

Multiple values can share a single case body using commas — no need for
`fallthrough`:

```go
func shouldEscape(c byte) bool {
    switch c {
    case ' ', '?', '&', '=', '#', '+', '%':
        return true
    }
    return false
}
```

---

## Break with Labels

`break` inside a `switch` terminates only the switch, **not** an enclosing
`for` loop. Use a label to break out of the loop:

```go
Loop:
    for n := 0; n < len(src); n += size {
        switch {
        case src[n] < sizeOne:
            break        // breaks switch only
        case src[n] < sizeTwo:
            if n+1 >= len(src) {
                break Loop   // breaks out of for loop
            }
        }
    }
```

Another common pattern — breaking a range loop from inside a switch:

```go
Loop:
    for _, v := range items {
        switch v.Type {
        case "done":
            break Loop  // breaks the for loop
        case "skip":
            break  // breaks only the switch
        }
    }
```

**Rule of thumb**: Whenever you have a `switch` inside a `for` and need to
exit the loop from a case, always use a labeled break.

---

## Type Switches

For type switches (`switch v := x.(type)`), see
[go-interfaces](../../go-interfaces/SKILL.md): Type Switch.

---

## Quick Reference

| Pattern | Syntax |
|---------|--------|
| Expression-less switch | `switch { case cond: }` |
| Comma cases | `case 'a', 'b', 'c':` |
| No fallthrough | Default; use `fallthrough` keyword if needed |
| Break switch only | `break` inside case |
| Break enclosing loop | `break Label` with labeled `for` |
    go-control-flow | Prompt Minder