English | 繁體中文

Go / Golang

Go is a compiled, statically typed language with garbage collection, explicit error values, structural interfaces, lightweight goroutines, and channels. Prefer simple, readable code; run gofmt and let go vet/tests guide refactoring.

Toolchain and modules

go mod init example.com/myapp       # create go.mod
go get example.com/lib@v1.2.3       # add or change a dependency
go mod tidy                         # synchronize go.mod/go.sum
go run .                           # compile and run
go build ./...                     # build all packages
go test ./... -race -cover         # tests, race detector, coverage
go vet ./...                       # suspicious constructs
gofmt -w .                         # canonical formatting
go doc fmt.Println                  # package/API documentation
go env GOPATH GOMOD GOVERSION

A directory is normally one package. A module can contain many packages. internal/ restricts imports to the parent tree; cmd/ commonly contains executables. Keep package names short and lowercase. Exported identifiers start with an uppercase letter and should have doc comments.

Syntax and core types

package main
 
import "fmt"
 
const (
    MaxRetries = 3
    _          = iota // iota is an untyped integer constant generator
)
 
var count int = 1
name := "Go" // short declaration; only inside functions
 
// Zero values: 0, false, "", nil pointer/slice/map/function/interface/channel.
var (
    n    int
    ok   bool
    text string
    nums []int       // nil slice is safe to range over and append to
    table map[string]int
)
 
// Built-ins: bool; signed/unsigned integers; float32/64; complex64/128;
// string (immutable bytes, usually UTF-8); byte = uint8; rune = int32.
// Conversions are explicit: int64(n), float64(n), []byte(text), string(data).
 
var (
    a [3]int              // array: length is part of its type
    s = []int{1, 2, 3}    // slice: pointer, length, capacity
    m = map[string]int{}  // map; lookup returns (value, found)
)
s = append(s, 4)
copy(s[1:], s[:2])
for key, value := range m { _ = key; _ = value }
 
// Structs group named fields; embedding promotes fields/methods.
type User struct {
    ID   int    `json:"id"` // struct tag, read by reflection/packages
    Name string `json:"name"`
}
u := User{ID: 1, Name: "Ada"}
ptr := &u
ptr.Name = "Grace" // automatic dereference for selectors
 
// Pointer: *T; address: &x; dereference: *p. There is no pointer arithmetic.

Control flow

if value, found := m["go"]; found {
    fmt.Println(value)
} else {
    fmt.Println("missing")
}
 
for i := 0; i < 3; i++ {}       // the only loop keyword
for condition() {}              // while-style
for range s {}                  // iterate values/indexes
switch name {
case "Go":
    // no implicit fall-through
case "Rust", "C":
    // comma-separated cases
 default:
}
 
// `break`, `continue`, and labeled break/continue are available.
// `goto` exists but is rarely appropriate.

Functions, methods, and interfaces

func divide(a, b int) (quotient int, err error) {
    if b == 0 { return 0, fmt.Errorf("divide by zero") }
    return a / b, nil
}
 
func sum(values ...int) int { // variadic values is []int inside the function
    total := 0
    for _, v := range values { total += v }
    return total
}
 
func (u User) Label() string { return u.Name }       // value receiver
func (u *User) Rename(name string) { u.Name = name } // pointer receiver
 
// Interfaces are satisfied implicitly: a type needs the required methods only.
type Stringer interface { String() string }
 
func printAny(v any) {
    switch x := v.(type) { // type switch
    case string:
        fmt.Println(x)
    case int:
        fmt.Println(x)
    default:
        fmt.Printf("%T\n", x)
    }
}
 
// `any` is an alias for interface{}; use a narrower interface when possible.
// Assertion: text, ok := v.(string). A failed assertion without `ok` panics.

Generics

type Number interface { ~int | ~int64 | ~float64 }
func Sum[T Number](xs []T) T {
    var total T
    for _, x := range xs { total += x }
    return total
}

~int includes named types whose underlying type is int. Type parameters may be constrained by interfaces; generic methods are not allowed to introduce their own type parameters.

Errors and resource safety

Errors are ordinary values. Return them; add context with %w; inspect with errors.Is/errors.As.

if err != nil {
    return fmt.Errorf("load user %d: %w", id, err)
}
if errors.Is(err, context.Canceled) { /* cancellation is expected */ }
var pathErr *os.PathError
if errors.As(err, &pathErr) { /* handle a typed error */ }

Use defer immediately after acquiring a resource: defer file.Close(), defer mu.Unlock(). Deferred calls run LIFO and capture arguments at the defer statement. panic is for unrecoverable programmer/invariant failures, not routine validation. recover only works in a deferred function in the same goroutine.

For untrusted filesystem paths, validate at the boundary (filepath.IsLocal, filepath.Localize) and, on Go 1.24+, consider os.OpenRoot/Root.OpenInRoot to constrain access to a directory.

Concurrency

  • go f(x) starts f in a goroutine; it shares memory with the caller.
  • A channel communicates values and synchronizes: make(chan T), buffered make(chan T, n).
  • The sender normally closes a channel; receiving from a closed channel yields the zero value and ok == false.
  • select waits for one ready channel operation; case <-ctx.Done() handles cancellation.
  • Never copy a sync.Mutex after first use. Protect shared state or use channels/atomics.
  • sync.WaitGroup joins goroutines; sync.Once initializes once; sync.Pool is for temporary reusable objects; sync.Map is specialized, not a default map replacement.
  • Use sync/atomic for small independent counters/state. Run go test -race ./...; a race is a correctness bug.
  • Every goroutine needs a termination path. Pass context.Context first, cancel derived contexts, and avoid goroutine/channel leaks.
  • A nil channel blocks forever; setting a channel variable to nil can disable a select case. Sending on a closed channel panics; closing twice panics. select { default: } is non-blocking.

Compilable example: cancellable worker pool, errors, generics, and JSON

Save as main.go in a module and run go run . (works with current Go releases).

package main
 
import (
    "context"
    "encoding/json"
    "errors"
    "fmt"
    "sync"
    "sync/atomic"
    "time"
)
 
type Job struct{ ID int; Text string }
type Result struct{ ID int; Length int; Err error }
 
// A generic helper: it works for any element type.
func Map[T, U any](xs []T, f func(T) U) []U {
    out := make([]U, len(xs))
    for i, x := range xs { out[i] = f(x) }
    return out
}
 
func worker(ctx context.Context, jobs <-chan Job, results chan<- Result, processed *atomic.Int64, wg *sync.WaitGroup) {
    defer wg.Done()
    for {
        select {
        case <-ctx.Done():
            return
        case job, ok := <-jobs:
            if !ok { return }
            if job.Text == "" {
                results <- Result{ID: job.ID, Err: errors.New("empty text")}
                continue
            }
            // Simulate I/O while remaining cancellable.
            select {
            case <-ctx.Done():
                return
            case <-time.After(1 * time.Millisecond):
            }
            processed.Add(1)
            results <- Result{ID: job.ID, Length: len([]rune(job.Text))}
        }
    }
}
 
func run(ctx context.Context, jobs []Job, workerCount int) ([]Result, int64, error) {
    ctx, cancel := context.WithCancel(ctx)
    defer cancel()
 
    jobCh := make(chan Job)
    resultCh := make(chan Result)
    var processed atomic.Int64
    var wg sync.WaitGroup
    wg.Add(workerCount)
    for i := 0; i < workerCount; i++ {
        go worker(ctx, jobCh, resultCh, &processed, &wg)
    }
 
    go func() {
        defer close(jobCh)
        for _, job := range jobs {
            select {
            case jobCh <- job:
            case <-ctx.Done():
                return
            }
        }
    }()
    go func() {
        wg.Wait()
        close(resultCh)
    }()
 
    results := make([]Result, 0, len(jobs))
    for result := range resultCh {
        results = append(results, result)
        if result.Err != nil {
            cancel() // stop remaining work after the first bad job
        }
    }
    for _, result := range results {
        if result.Err != nil {
            return results, processed.Load(), fmt.Errorf("job %d: %w", result.ID, result.Err)
        }
    }
    return results, processed.Load(), nil
}
 
func main() {
    jobs := []Job{{1, "gopher"}, {2, "并发"}, {3, ""}}
    results, processed, err := run(context.Background(), jobs, 2)
    payload, marshalErr := json.Marshal(results)
    if marshalErr != nil { panic(marshalErr) }
    fmt.Printf("processed=%d results=%s err=%v\n", processed, payload, err)
 
    lengths := Map([]string{"go", "is", "fun"}, func(s string) int { return len(s) })
    fmt.Println("lengths:", lengths)
}

The example demonstrates receive-only/send-only channel types, cancellation, channel ownership/closing, WaitGroup, atomic state, a generic function, multiple returns, wrapped errors, Unicode rune counting, and JSON. It intentionally reports the empty job as an error while shutting down workers cleanly.

Standard-library map

  • Text/data: strings, bytes, strconv, unicode/utf8, encoding/json, encoding/csv, regexp.
  • Files/paths: io, os, path/filepath, bufio, embed.
  • HTTP/networking: net/http, net/url, net, httptest; always set timeouts on clients/servers.
  • Time/context: time, context; pass context through call chains, do not store it in structs.
  • Collections/algorithms: sort, slices, maps, container/heap.
  • System/concurrency: sync, sync/atomic, runtime, os/signal.
  • Observability: log/slog, expvar, runtime/pprof, net/http/pprof.

Testing, performance, and design checklist

func TestSum(t *testing.T) {
    got := Sum([]int{1, 2, 3})
    if got != 6 { t.Fatalf("got %d, want 6", got) }
}

Use table-driven tests, subtests (t.Run), benchmarks (go test -bench .), fuzz tests (go test -fuzz=.), and httptest for HTTP. Prefer black-box package tests (package x_test) when testing public behavior. Benchmark before optimizing; use -benchmem, pprof, and allocation profiles. Avoid unnecessary allocations; use strings.Builder for repeated string construction, but favor clarity first.

Idioms: small interfaces owned by consumers; accept interfaces and return concrete types; composition/embedding over inheritance; keep constructors validating invariants; make zero values useful; avoid global mutable state; document exported APIs; use defer for cleanup; never ignore an error without a deliberate reason; keep goroutine ownership and shutdown explicit.